Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
round_superellipse_geometry.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <cmath>
6#include <variant>
7
10
12
13namespace impeller {
14
15namespace {
16
17constexpr auto kGapFactor = RoundSuperellipseParam::kGapFactor;
18
19// An interface for classes that arranges a point list that forms a convex
20// contour into a triangle strip.
21class ConvexRearranger {
22 public:
23 ConvexRearranger() {}
24
25 virtual ~ConvexRearranger() {}
26
27 virtual size_t ContourLength() const = 0;
28
29 virtual Point GetPoint(size_t i) const = 0;
30
31 void RearrangeIntoTriangleStrip(Point* output) {
32 size_t index_count = 0;
33
34 output[index_count++] = GetPoint(0);
35
36 size_t a = 1;
37 size_t contour_length = ContourLength();
38 size_t b = contour_length - 1;
39 while (a < b) {
40 output[index_count++] = GetPoint(a);
41 output[index_count++] = GetPoint(b);
42 a++;
43 b--;
44 }
45 if (a == b) {
46 output[index_count++] = GetPoint(b);
47 }
48 }
49
50 private:
51 ConvexRearranger(const ConvexRearranger&) = delete;
52 ConvexRearranger& operator=(const ConvexRearranger&) = delete;
53};
54
55// A convex rearranger whose contour is concatenated from 4 quadrant segments.
56//
57// The input quadrant curves must travel from the Y axis to the X axis, and
58// include both ends. This means that the points on the axes are duplicate
59// between segments, and will be omitted by this class.
60class UnevenQuadrantsRearranger : public ConvexRearranger {
61 public:
62 UnevenQuadrantsRearranger(Point* cache, size_t segment_capacity)
63 : cache_(cache), segment_capacity_(segment_capacity) {}
64
65 Point* QuadCache(size_t i) { return cache_ + segment_capacity_ * i; }
66
67 const Point* QuadCache(size_t i) const {
68 return cache_ + segment_capacity_ * i;
69 }
70
71 size_t& QuadSize(size_t i) { return lengths_[i]; }
72
73 size_t ContourLength() const override {
74 return lengths_[0] + lengths_[1] + lengths_[2] + lengths_[3] - 4;
75 }
76
77 Point GetPoint(size_t i) const override {
78 // output from index
79 // 0 ... l0-2 quads[0] 0 ... l0-2
80 // next 0 ... l1-2 quads[1] l1-1 ... 1
81 // next 0 ... l2-2 quads[2] 0 ... l2-2
82 // next 0 ... l3-2 quads[3] l3-1 ... 1
83 size_t high = lengths_[0] - 1;
84 if (i < high) {
85 return QuadCache(0)[i];
86 }
87 high += lengths_[1] - 1;
88 if (i < high) {
89 return QuadCache(1)[high - i];
90 }
91 size_t low = high;
92 high += lengths_[2] - 1;
93 if (i < high) {
94 return QuadCache(2)[i - low];
95 }
96 high += lengths_[3] - 1;
97 if (i < high) {
98 return QuadCache(3)[high - i];
99 } else {
100 // Unreachable
101 return Point();
102 }
103 }
104
105 private:
106 Point* cache_;
107 size_t segment_capacity_;
108 size_t lengths_[4];
109};
110
111// A convex rearranger whose contour is concatenated from 4 identical quadrant
112// segments.
113//
114// The input curve must travel from the Y axis to the X axis and include both
115// ends. This means that the points on the axes are duplicate between segments,
116// and will be omitted by this class.
117class MirroredQuadrantRearranger : public ConvexRearranger {
118 public:
119 MirroredQuadrantRearranger(Point center, Point* cache)
120 : center_(center), cache_(cache) {}
121
122 size_t& QuadSize() { return l_; }
123
124 size_t ContourLength() const override { return l_ * 4 - 4; }
125
126 Point GetPoint(size_t i) const override {
127 // output from index
128 // 0 ... l-2 quad 0 ... l-2
129 // next 0 ... l-2 quad l-1 ... 1
130 // next 0 ... l-2 quad 0 ... l-2
131 // next 0 ... l-2 quad l-1 ... 1
132 size_t high = l_ - 1;
133 if (i < high) {
134 return cache_[i] + center_;
135 }
136 high += l_ - 1;
137 if (i < high) {
138 return cache_[high - i] * Point{1, -1} + center_;
139 }
140 size_t low = high;
141 high += l_ - 1;
142 if (i < high) {
143 return cache_[i - low] * Point{-1, -1} + center_;
144 }
145 high += l_ - 1;
146 if (i < high) {
147 return cache_[high - i] * Point{-1, 1} + center_;
148 } else {
149 // Unreachable
150 return Point();
151 }
152 }
153
154 private:
155 Point center_;
156 Point* cache_;
157 size_t l_ = 0;
158};
159
160// A matrix that swaps the coordinates of a point.
161// clang-format off
162constexpr Matrix kFlip = Matrix(
163 0.0f, 1.0f, 0.0f, 0.0f,
164 1.0f, 0.0f, 0.0f, 0.0f,
165 0.0f, 0.0f, 1.0f, 0.0f,
166 0.0f, 0.0f, 0.0f, 1.0f);
167// clang-format on
168
169// The max angular step that the algorithm will traverse a quadrant of the
170// curve.
171//
172// This limits the max number of points of the curve.
173constexpr Scalar kMaxQuadrantSteps = 40;
174
175// Calculates the angular step size for a smooth curve.
176//
177// Returns the angular step needed to ensure a curve appears smooth
178// based on the smallest dimension of a shape. Smaller dimensions require
179// larger steps as less detail is needed for smoothness.
180//
181// The `minDimension` is the smallest dimension (e.g., width or height) of the
182// shape.
183//
184// The `fullAngle` is the total angular range to traverse.
185Scalar CalculateStep(Scalar minDimension, Scalar fullAngle) {
186 constexpr Scalar kMinAngleStep = kPiOver2 / kMaxQuadrantSteps;
187
188 // Assumes at least 1 point is needed per pixel to achieve sufficient
189 // smoothness.
190 constexpr Scalar pointsPerPixel = 1.0;
191 size_t pointsByDimension =
192 static_cast<size_t>(std::ceil(minDimension * pointsPerPixel));
193 Scalar angleByDimension = fullAngle / pointsByDimension;
194
195 return std::min(kMinAngleStep, angleByDimension);
196}
197
198// Draw a superellipsoid arc.
199//
200// The superellipse is centered at the origin and has degree `n` and both
201// semi-axes equal to `a`. The arc starts from positive Y axis and spans from 0
202// to `max_theta` radiance clockwise if `reverse` is false, or from `max_theta`
203// to 0 otherwise.
204//
205// The resulting points, transformed by `transform`, are appended to `output`.
206// The starting point is included, but the ending point is excluded.
207//
208// Returns the number of points generated.
209size_t DrawSuperellipsoidArc(Point* output,
210 Scalar a,
211 Scalar n,
212 Scalar max_theta,
213 bool reverse,
214 const Matrix& transform) {
215 Point* next = output;
216 Scalar angle = reverse ? max_theta : 0.0f;
217 Scalar step =
218 (reverse ? -1 : 1) *
219 CalculateStep(a - a * pow(abs(cosf(max_theta)), 2 / n), max_theta);
220 Scalar end = reverse ? 0.0f : max_theta;
221 while ((angle < end) != reverse) {
222 Scalar x = a * pow(abs(sinf(angle)), 2 / n);
223 Scalar y = a * pow(abs(cosf(angle)), 2 / n);
224 *(next++) = transform * Point(x, y);
225 angle += step;
226 }
227 return next - output;
228}
229
230// Draws a circular arc centered at the origin with a radius of `r`, starting at
231// `start`, and spanning `max_angle` clockwise.
232//
233// If `reverse` is false, points are generated from `start` to `start +
234// max_angle`. If `reverse` is true, points are generated from `start +
235// max_angle` back to `start`.
236//
237// The generated points, transformed by `transform`, are appended to `output`.
238// The starting point is included, but the ending point is excluded.
239//
240// Returns the number of points generated.
241size_t DrawCircularArc(Point* output,
242 Point start,
243 Scalar max_angle,
244 bool reverse,
245 const Matrix& transform) {
246 /* Denote the middle point of S and E as M. The key is to find the center of
247 * the circle.
248 * S --__
249 * / ⟍ `、
250 * / M ⟍\
251 * / ⟋ E
252 * / ⟋ ↗
253 * / ⟋
254 * / ⟋ r
255 * C ᜱ ↙
256 */
257
258 Point end = start.Rotate(Radians(-max_angle));
259
260 Point* next = output;
261 Scalar angle = reverse ? max_angle : 0.0f;
262 Scalar step =
263 (reverse ? -1 : 1) * CalculateStep(std::abs(start.y - end.y), max_angle);
264 Scalar end_angle = reverse ? 0.0f : max_angle;
265
266 while ((angle < end_angle) != reverse) {
267 *(next++) = transform * start.Rotate(Radians(-angle));
268 angle += step;
269 }
270 return next - output;
271}
272
273// Draws an arc representing the top 1/8 segment of a square-like rounded
274// superellipse centered at the origin.
275//
276// If `reverse_and_flip` is false, the resulting arc spans from 0 (inclusive) to
277// pi/4 (exclusive), moving clockwise starting from the positive Y-axis. If
278// `reverse` is true, the curve spans from pi/4 (inclusive) to 0 (inclusive)
279// counterclockwise instead, and all points have their x and y coordinates
280// flipped.
281//
282// Either way, each point is then transformed by `external_transform` and
283// appended to `output`.
284//
285// Returns the number of points generated.
286size_t DrawOctantSquareLikeSquircle(Point* output,
287 const RoundSuperellipseParam::Octant& param,
288 bool reverse_and_flip,
289 const Matrix& external_transform) {
290 Matrix transform = external_transform * Matrix::MakeTranslation(param.offset);
291 if (reverse_and_flip) {
292 transform = transform * kFlip;
293 }
294 if (param.se_n < 2) {
295 // It's a square.
296 *output = transform * Point(param.se_a, param.se_a);
297 return 1;
298 }
299
300 /* The following figure shows the first quadrant of a square-like rounded
301 * superellipse. The target arc consists a superellipsoid arc (AJ) and a
302 * circular arc (JM).
303 *
304 * superelipse
305 * A ↓ circular arc
306 * ---------...._J ↙
307 * | / `⟍ M (where x=y)
308 * | / ⟋ ⟍
309 * | / ⟋ \
310 * | / ⟋ |
311 * | ᜱD |
312 * | ⟋ |
313 * | ⟋ |
314 * |⟋ |
315 * +--------------------| A'
316 * O
317 * ←-------- a ---------→
318 */
319
320 Point* next = output;
321 if (!reverse_and_flip) {
322 // Arc [A, J)
323 next +=
324 DrawSuperellipsoidArc(next, param.se_a, param.se_n, param.se_max_theta,
325 reverse_and_flip, transform);
326 // Arc [J, M)
327 next += DrawCircularArc(
328 next, param.circle_start - param.circle_center,
329 param.circle_max_angle.radians, reverse_and_flip,
330 transform * Matrix::MakeTranslation(param.circle_center));
331 } else {
332 // Arc [M, J)
333 next += DrawCircularArc(
334 next, param.circle_start - param.circle_center,
335 param.circle_max_angle.radians, reverse_and_flip,
336 transform * Matrix::MakeTranslation(param.circle_center));
337 // Arc [J, A)
338 next +=
339 DrawSuperellipsoidArc(next, param.se_a, param.se_n, param.se_max_theta,
340 reverse_and_flip, transform);
341 // Point A
342 *(next++) = transform * Point(0, param.se_a);
343 }
344 return next - output;
345}
346
347// Draw a quadrant curve, both ends included.
348//
349// Returns the number of points.
350static size_t DrawQuadrant(Point* output,
351 const RoundSuperellipseParam::Quadrant& param) {
352 Point* next = output;
353 auto transform = Matrix::MakeTranslateScale(param.signed_scale, param.offset);
354
355 next += DrawOctantSquareLikeSquircle(next, param.top,
356 /*reverse_and_flip=*/false, transform);
357
358 next += DrawOctantSquareLikeSquircle(next, param.right,
359 /*reverse_and_flip=*/true, transform);
360
361 return next - output;
362}
363
364} // namespace
365
367 const RoundingRadii& radii)
368 : bounds_(bounds.GetPositive()), radii_(radii.Scaled(bounds_)) {}
369
371 float corner_radius)
373 RoundingRadii::MakeRadius(corner_radius)) {}
374
376
377GeometryResult RoundSuperellipseGeometry::GetPositionBuffer(
378 const ContentContext& renderer,
379 const Entity& entity,
380 RenderPass& pass) const {
381 Point* cache = renderer.GetTessellator().GetStrokePointCache().data();
382
383 // The memory size (in units of Points) allocated to store each quadrants.
384 constexpr size_t kMaxQuadSize = kPointArenaSize / 4;
385 // Since the curve is traversed in steps bounded by kMaxQuadrantSteps, the
386 // curving part will have fewer points than kMaxQuadrantSteps. Multiply it by
387 // 2 for storing other sporatic points (an extremely conservative estimate).
388 static_assert(kMaxQuadSize > 2 * kMaxQuadrantSteps);
389
390 ConvexRearranger* rearranger;
391 std::variant<std::monostate, MirroredQuadrantRearranger,
392 UnevenQuadrantsRearranger>
393 rearranger_holder;
394
395 auto param = RoundSuperellipseParam::MakeBoundsRadii(bounds_, radii_);
396
397 if (param.all_corners_same) {
398 rearranger_holder.emplace<MirroredQuadrantRearranger>(bounds_.GetCenter(),
399 cache);
400 auto& t = std::get<MirroredQuadrantRearranger>(rearranger_holder);
401 rearranger = &t;
402
403 // The quadrant must be drawn at the origin so that it can be rotated later.
404 param.top_right.offset = Point();
405 t.QuadSize() = DrawQuadrant(cache, param.top_right);
406 } else {
407 rearranger_holder.emplace<UnevenQuadrantsRearranger>(cache, kMaxQuadSize);
408 auto& t = std::get<UnevenQuadrantsRearranger>(rearranger_holder);
409 rearranger = &t;
410
411 t.QuadSize(0) = DrawQuadrant(t.QuadCache(0), param.top_right);
412 t.QuadSize(1) = DrawQuadrant(t.QuadCache(1), param.bottom_right);
413 t.QuadSize(2) = DrawQuadrant(t.QuadCache(2), param.bottom_left);
414 t.QuadSize(3) = DrawQuadrant(t.QuadCache(3), param.top_left);
415 }
416
417 size_t contour_length = rearranger->ContourLength();
418 BufferView vertex_buffer = renderer.GetTransientsDataBuffer().Emplace(
419 nullptr, sizeof(Point) * contour_length, alignof(Point));
420 Point* vertex_data =
421 reinterpret_cast<Point*>(vertex_buffer.GetBuffer()->OnGetContents() +
422 vertex_buffer.GetRange().offset);
423 rearranger->RearrangeIntoTriangleStrip(vertex_data);
424 vertex_buffer.GetBuffer()->Flush(vertex_buffer.GetRange());
425
426 return GeometryResult{
428 .vertex_buffer =
429 {
430 .vertex_buffer = vertex_buffer,
431 .vertex_count = contour_length,
432 .index_type = IndexType::kNone,
433 },
434 .transform = entity.GetShaderTransform(pass),
435 };
436}
437
438std::optional<Rect> RoundSuperellipseGeometry::GetCoverage(
439 const Matrix& transform) const {
440 return bounds_.TransformBounds(transform);
441}
442
444 const IRect& rect) const {
445 if (!transform.IsTranslationScaleOnly()) {
446 return false;
447 }
448 Scalar left_inset = std::max(radii_.top_left.width, radii_.bottom_left.width);
449 Scalar right_inset =
450 std::max(radii_.top_right.width, radii_.bottom_right.width);
451 Scalar top_inset = std::max(radii_.top_left.height, radii_.top_right.height);
452 Scalar bottom_inset =
453 std::max(radii_.bottom_left.height, radii_.bottom_right.height);
454 Rect coverage =
455 Rect::MakeLTRB(bounds_.GetLeft() + left_inset * kGapFactor,
456 bounds_.GetTop() + top_inset * kGapFactor,
457 bounds_.GetRight() - right_inset * kGapFactor,
458 bounds_.GetBottom() - bottom_inset * kGapFactor);
459 return coverage.TransformBounds(transform).Contains(rect);
460}
461
463 return false;
464}
465
467 const RoundSuperellipse& round_superellipse,
468 const StrokeParameters& parameters)
469 : StrokePathSourceGeometry(parameters),
470 round_superellipse_source_(round_superellipse) {}
471
473 return round_superellipse_source_;
474}
475
476} // namespace impeller
HostBuffer & GetTransientsDataBuffer() const
Retrieve the current host buffer for transient storage of other non-index data.
Tessellator & GetTessellator() const
Matrix GetShaderTransform(const RenderPass &pass) const
Definition entity.cc:50
BufferView Emplace(const BufferType &buffer, size_t alignment=0)
Emplace non-uniform data (like contiguous vertices) onto the host buffer.
Definition host_buffer.h:92
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition render_pass.h:30
A Geometry class that generates fillable vertices (with or without texture coordinates) directly from...
RoundSuperellipseGeometry(const Rect &bounds, const RoundingRadii &radii)
bool CoversArea(const Matrix &transform, const IRect &rect) const override
Determines if this geometry, transformed by the given transform, will completely cover all of the pix...
An abstract Geometry base class that produces fillable vertices representing the stroked outline from...
StrokeRoundSuperellipseGeometry(const RoundSuperellipse &round_superellipse, const StrokeParameters &parameters)
std::vector< Point > & GetStrokePointCache()
Retrieve a pre-allocated arena of kPointArenaSize points.
int32_t x
double y
float Scalar
Definition scalar.h:19
@ kNone
Does not use the index buffer.
TPoint< Scalar > Point
Definition point.h:426
constexpr float kPiOver2
Definition constants.h:32
static constexpr size_t kPointArenaSize
The size of the point arena buffer stored on the tessellator.
Definition tessellator.h:25
A 4x4 matrix using column-major storage.
Definition matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition matrix.h:95
static constexpr Matrix MakeTranslateScale(const Vector3 &s, const Vector3 &t)
Definition matrix.h:113
static RoundSuperellipseParam MakeBoundsRadii(const Rect &bounds, const RoundingRadii &radii)
A structure to store all of the parameters related to stroking a path or basic geometry object.
constexpr auto GetBottom() const
Definition rect.h:391
constexpr TRect TransformBounds(const Matrix &transform) const
Creates a new bounding box that contains this transformed rectangle.
Definition rect.h:506
constexpr auto GetTop() const
Definition rect.h:387
constexpr bool Contains(const TPoint< Type > &p) const
Returns true iff the provided point |p| is inside the half-open interior of this rectangle.
Definition rect.h:255
constexpr auto GetLeft() const
Definition rect.h:385
constexpr auto GetRight() const
Definition rect.h:389
constexpr Point GetCenter() const
Get the center point as a |Point|.
Definition rect.h:416
static constexpr TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition rect.h:129
Type height
Definition size.h:29
Type width
Definition size.h:28
const size_t start
const size_t end