Flutter Engine
 
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 Rect& 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:48
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...
bool CoversArea(const Matrix &transform, const Rect &rect) const override
Determines if this geometry, transformed by the given transform, will completely cover all surface ar...
RoundSuperellipseGeometry(const Rect &bounds, const RoundingRadii &radii)
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:327
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:357
constexpr TRect TransformBounds(const Matrix &transform) const
Creates a new bounding box that contains this transformed rectangle.
Definition rect.h:472
constexpr auto GetTop() const
Definition rect.h:353
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:231
constexpr auto GetLeft() const
Definition rect.h:351
constexpr auto GetRight() const
Definition rect.h:355
constexpr Point GetCenter() const
Get the center point as a |Point|.
Definition rect.h:382
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