Flutter Impeller
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 
13 namespace impeller {
14 
15 namespace {
16 
17 constexpr 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.
21 class 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.
60 class 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.
117 class 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
162 constexpr 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.
173 constexpr 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.
185 Scalar 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.
209 size_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.
241 size_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.
286 size_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.
350 static 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)
372  : RoundSuperellipseGeometry(bounds,
373  RoundingRadii::MakeRadius(corner_radius)) {}
374 
376 
377 GeometryResult 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.GetTransientsBuffer().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 
438 std::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 & GetTransientsBuffer() const
Retrieve the currnent host buffer for transient storage.
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.
Definition: tessellator.cc:306
int32_t x
@ kNone
Does not use the index buffer.
float Scalar
Definition: scalar.h:19
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:361
constexpr TRect TransformBounds(const Matrix &transform) const
Creates a new bounding box that contains this transformed rectangle.
Definition: rect.h:476
constexpr auto GetTop() const
Definition: rect.h:357
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:235
constexpr auto GetLeft() const
Definition: rect.h:355
constexpr auto GetRight() const
Definition: rect.h:359
constexpr Point GetCenter() const
Get the center point as a |Point|.
Definition: rect.h:386
constexpr static 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