Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
shadow_path_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
6
10
11namespace {
12
16using impeller::Point;
21using impeller::Trig;
23
24/// Each point in the polygon form of the path is turned into a structure
25/// that tracks the gradient of the shadow at that point in the path. The
26/// shape is turned into a sort of pin cushion where each struct acts
27/// like a pin pushed into that cushion in the direction of the shadow
28/// gradient at that location.
29///
30/// Each entry contains the direction of the pin at that location and the
31/// depth to which the pin is inserted, expressed as a fraction of the full
32/// umbra size indicated by the shadow parameters. A depth of 1.0 means
33/// the pin was inserted all the way to the depth of the shadow gradient
34/// and didn't collide with any other pins. A fraction less than 1.0 can
35/// occur if either the shape was too small and the pins intersected with
36/// other pins across the shape from them, or if the curvature in a given
37/// area was so tight that adjacent pins started bumping into their neighbors
38/// even if the overall size of the shape was larger than the shadow.
39///
40/// Different pins will be shortened by different amounts in the same shape
41/// depending on their local geometry (tight curves or narrow cross section).
42struct UmbraPin {
43 /// An initial value for the pin fraction that indicates that we have
44 /// not yet visited this pin during the clipping process.
45 static constexpr Scalar kFractionUninitialized = -1.0f;
46
47 /// The point on the original path that generated this entry into the
48 /// umbra geometry.
49 ///
50 /// AKA the point on the path at which this pin was stabbed.
51 Point path_vertex;
52
53 /// The relative vector from this path segment to the next.
54 Vector2 path_delta;
55
56 /// The vector from the path_vertex to the head of the pin (the part
57 /// outside the shape).
58 Vector2 penumbra_delta;
59
60 /// The location of the end of this pin, taking into account the reduction
61 /// of the umbra_size due to minimum distance to centroid, but ignoring
62 /// clipping against other pins.
63 Point pin_tip;
64
65 /// The location that this pin confers to the umbra polygon. Initially,
66 /// this is the same as the pin_tip, but can be reduced by intersecting
67 /// and clipping against other pins and even eliminated if the other
68 /// nearby pins make it redundant for defining the umbra polygon.
69 ///
70 /// Redundant or "removed" pins are indicated by no longer being a part
71 /// of the linked list formed by the |p_next| and |p_prev| pointers.
72 ///
73 /// Eventually, if this pin's umbra_vertex was eliminated, this location
74 /// will be overwritten by the surviving umbra vertex that best servies
75 /// this pin's path_vertex in a follow-on step.
76 Point umbra_vertex;
77
78 /// The index in the vertices vector where the umbra_vertex is eventually
79 /// inserted. Used to enter triangles into the indices vector.
80 uint16_t umbra_index = 0u;
81
82 /// The interior penetration of the umbra starts out at the full blur
83 /// radius as modified by the global distance of the path segments to
84 /// the centroid, but can be shortened when pins are too crowded and start
85 /// intersecting each other due to tight curvature.
86 ///
87 /// It's initial value is actually the uninitialized constant so that the
88 /// algorithm can treat it specially the first time it is encountered.
89 Scalar umbra_fraction = kFractionUninitialized;
90
91 /// Pointers used to create a circular linked list while pruning the umbra
92 /// polygon. The final list of vertices that remain in the umbra polygon
93 /// are the vertices that remain on this linked list from a "head" pin.
94 UmbraPin* p_next = nullptr;
95 UmbraPin* p_prev = nullptr;
96
97 /// Returns true after the umbra_fraction is first initialized to a real
98 /// value representing its potential intersections with other pins. At
99 /// that point it will be a number from 0 to 1.
100 bool IsFractionInitialized() const {
101 return umbra_fraction > kFractionUninitialized;
102 }
103};
104
105/// Simple cross products of nearby vertices don't catch all cases of
106/// non-convexity so we count the number of times that the sign of the
107/// dx/dy of the edges change. It must be <= 3 times for the path to
108/// be convex. Think of drawing a circle from the top. First you head
109/// to the right, then reverse to the left as you round the bottom of
110/// the circle, then back near the top you head to the right again,
111/// totalling 3 changes in direction.
112struct DirectionDetector {
113 Scalar last_direction_ = 0.0f;
114 size_t change_count = 0u;
115
116 /// Check the coordinate delta for a new polygon edge to see if it
117 /// represents another change in direction for the path on this axis.
118 void AccumulateDirection(Scalar new_direction) {
119 if (last_direction_ == 0.0f || last_direction_ * new_direction < 0.0f) {
120 last_direction_ = std::copysign(1.0f, new_direction);
121 change_count++;
122 }
123 }
124
125 /// Returns true if the path must be concave.
126 bool IsConcave() const {
127 // See comment above on the struct for why 3 changes is the most you
128 // should see in a convex path.
129 return change_count > 3u;
130 }
131};
132
133/// Utility class to receive the vertices of a path and turn them into
134/// a vector of UmbraPins along with a centroid Point.
135///
136/// The class will immediately flag and stop processing any path that
137/// has more than one contour since algorithms of the nature implemented
138/// here won't be able to process such paths.
139///
140/// The class will also flag and stop processing any path that has a
141/// non-convex section because the current algorithm only works for convex
142/// paths. Though it is possible to improve the algorithm to handle
143/// concave single-contour paths in the future as the Skia utilities
144/// provide a solution for those paths.
145class UmbraPinAccumulator : public PathTessellator::VertexWriter {
146 public:
147 /// Parameters that determine the sub-pixel grid we will use to simplify
148 /// the contours to avoid degenerate differences in the vertices.
149 /// These 2 constants are a pair used in the implementation of the
150 /// ToDeviceGrid method and must be reciprocals of each other.
151 ///
152 /// @see ToPixelGrid
153 static constexpr Scalar kSubPixelCount = 16.0f;
154 static constexpr Scalar kSubPixelScale = (1.0f / kSubPixelCount);
155
156 /// The classification status of the path after all of the points are
157 /// accumulated.
158 enum class PathStatus {
159 /// The path was empty either because it contained no points or
160 /// because they enclosed no area.
161 kEmpty,
162
163 /// The path was complete, a single contour, and convex all around.
164 kConvex,
165
166 /// The path violated one of the conditions of convexity. Either it
167 /// had points that turned different ways along its perimeter, or it
168 /// turned more than 360 degrees, or it self-intersected.
169 kNonConvex,
170
171 /// The path had multiple contours.
172 kMultipleContours,
173 };
174
175 explicit UmbraPinAccumulator(Vector2 device_scale);
176
177 ~UmbraPinAccumulator() = default;
178
179 /// Reserve enough pins for the indicated number of path vertices to
180 /// avoid having to grow the vector during processing.
181 void Reserve(size_t vertex_count) { pins_.reserve(vertex_count); }
182
183 /// Return the status properties of the path.
184 /// see |PathStatus|
185 PathStatus GetStatus() { return GetResults().status; }
186
187 /// Returns a reference to the accumulated vector of UmbraPin structs.
188 /// Only valid if the status is kConvex.
189 std::vector<UmbraPin>& GetPins() { return pins_; }
190
191 /// Returns the centroid of the path.
192 /// Only valid if the status is kConvex.
193 Point GetCentroid() { return GetResults().centroid; }
194
195 /// Returns the turning direction of the path.
196 /// Only valid if the status is kConvex.
197 Scalar GetDirection() { return GetResults().path_direction; }
198
199 private:
200 /// The data computed when completing (finalizing) the analysis of the
201 /// path.
202 struct PathResults {
203 /// The type of path determined during the final analysis.
204 PathStatus status;
205
206 /// The centroid ("center of mass") of the path around which we will
207 /// build the shadow mesh.
208 Point centroid;
209
210 /// The direction of the path as determined by cross products. This
211 /// value is important to further processing to know when pins are
212 /// intersecting each other as the calculations for that condition
213 /// depend on the direction of the path.
214 Scalar path_direction = 0.0f;
215 };
216
217 // |VertexWriter|
218 void Write(Point point) override;
219
220 // |VertexWriter|
221 void EndContour() override;
222
223 /// Rounds the device coordinate to the sub-pixel grid.
224 Point ToDeviceGrid(Point point);
225
226 const Point device_scale_;
227
228 /// The list of pins being accumulated for further processing by the
229 /// mesh generation code.
230 std::vector<UmbraPin> pins_;
231
232 /// Internal state variable used by the VertexWriter callbacks to know
233 /// if the path contained multiple contours. It is set to true when the
234 /// first contour is ended by a call to EndContour().
235 bool first_contour_ended_ = false;
236
237 /// Internal state variable used by the VertexWriter callbacks to know
238 /// if the path contained multiple contours. It is set to true if additional
239 /// path points are delivered after the first contour is ended.
240 bool has_multiple_contours_ = false;
241
242 /// The results of finalizing the analysis of the path, set only after
243 /// the final analysis method is run.
244 std::optional<PathResults> results_;
245
246 /// Finalize the path analysis if necessary and return the structure with
247 /// the results of the analysis.
248 PathResults& GetResults() {
249 if (results_.has_value()) {
250 return results_.value();
251 }
252 return (results_ = FinalizePath()).value();
253 }
254
255 /// Run through the accumulated, de-duplicated, de-collinearized points
256 /// and check for a convex, non-self-intersecting path.
257 PathResults FinalizePath();
258};
259
260/// The |PolygonInfo| class does most of the work of generating a mesh from
261/// a path, including transforming it into device space, computing new vertices
262/// by applying the inset and outset for the indicated occluder_height, and
263/// then stitching all of those vertices together into a mesh that can be
264/// used to render the shadow complete with gaussian coefficients for the
265/// location of the mesh points within the shadow.
266class PolygonInfo {
267 public:
268 /// Return the radius of the rounded corners of the shadow for the
269 /// indicated occluder_height.
270 static constexpr Scalar GetTrigRadiusForHeight(Scalar occluder_height) {
271 return GetPenumbraSizeForHeight(occluder_height);
272 }
273
274 /// Construct a PolygonInfo that will accept a path and compute a shadow
275 /// mesh at the indicated occluder_height.
276 explicit PolygonInfo(Scalar occluder_height);
277
278 /// Computes a shadow mesh for the indicated path (source) under the
279 /// given matrix with the associated trigs. If the algorithm is successful,
280 /// it will return the resulting mesh (which may be empty if the path
281 /// contained no area) or nullptr if it was unable to process the path.
282 ///
283 /// @param source The PathSource object that delivers the path segments
284 /// that define the path being shadowed.
285 /// @param matrix The transform matrix under which the shadow is being
286 /// viewed.
287 /// @param trigs The Trigs array that contains precomputed sin and cos
288 /// values for a flattened arc at the required radius for
289 /// rounding out the edges of the shadow as we turn corners
290 /// in the path.
291 ///
292 /// @see GetTrigRadiusForHeight
293 const std::shared_ptr<ShadowVertices> CalculateConvexShadowMesh(
294 const impeller::PathSource& source,
295 const impeller::Matrix& matrix,
296 const Tessellator::Trigs& trigs);
297
298 private:
299 /// Compute the size of the penumbra for a given occluder_height which
300 /// can vary depending on the type of shadow. Here we are only processing
301 /// ambient shadows.
302 static constexpr Scalar GetPenumbraSizeForHeight(Scalar occluder_height) {
303 return occluder_height;
304 }
305
306 /// Compute the size of the umbra for a given occluder_height which
307 /// can vary depending on the type of shadow. Here we are only processing
308 /// ambient shadows.
309 static constexpr Scalar GetUmbraSizeForHeight(Scalar occluder_height) {
310 return occluder_height;
311 }
312
313 /// The minimum distance (squared) between points on the mesh before we
314 /// eliminate them as redundant.
315 static constexpr Scalar kMinSubPixelDistanceSquared =
316 UmbraPinAccumulator::kSubPixelScale * UmbraPinAccumulator::kSubPixelScale;
317
318 /// The occluder_height for which we are processing this shadow.
319 const Scalar occluder_height_;
320
321 /// The maximum gaussian of the umbra part of the shadow, usually 1.0f
322 /// but can be reduced if the umbra size was clipped.
323 Scalar umbra_gaussian_ = 1.0f;
324
325 /// The vertex mesh result that represents the shadow, to be rendered
326 /// using a modified indexed variant of DrawVertices that also adjusts
327 /// the alpha of the colors on a per-pixel basis by mapping their linear
328 /// gaussian coefficients into the associated gaussian integral values.
329
330 /// vertices_ stores all of the points in the mesh.
331 std::vector<Point> vertices_;
332
333 /// indices_ stores the indexes of the triangles in the mesh, in a
334 /// raw triangle format (i.e. not a triangle fan or strip).
335 std::vector<uint16_t> indices_;
336
337 /// gaussians_ stores the gaussian values associated with each vertex
338 /// in the mesh, the values being 1:1 with the equivalent vertex in
339 /// the vertices_ vedtor.
340 std::vector<Scalar> gaussians_;
341
342 /// Run through the pins and determine the closest pin to the centroid
343 /// and, in particular, adjust the umbra_gaussian value if the closest pin
344 /// is less than the required umbra distance.
345 void ComputePinDirectionsAndMinDistanceToCentroid(std::vector<UmbraPin>& pins,
346 const Point& centroid,
347 Scalar direction);
348
349 /// The head and count for the list of UmbraPins that contribute to the
350 /// umbra vertex ring.
351 ///
352 /// The forward and backward pointers for the linked list are stored
353 /// in the UmbraPin struct as p_next, p_prev.
354 struct UmbraPinLinkedList {
355 UmbraPin* p_head_pin = nullptr;
356 size_t pin_count = 0u;
357
358 bool IsNull() { return p_head_pin == nullptr; }
359 };
360
361 /// Run through the pins and determine if they intersect each other
362 /// internally, whether they are completely obscured by other pins,
363 /// their new relative lengths if they defer to another pin at some
364 /// depth, and which remaining pins are part of the umbra polygon,
365 /// and then return the pointer to the first pin in the "umbra polygon".
366 UmbraPinLinkedList ResolveUmbraIntersections(std::vector<UmbraPin>& pins,
367 Scalar direction);
368
369 /// Structure to store the result of computing the intersection between
370 /// 2 pins, pin0 and pin1, containing the point of intersection and the
371 /// relative fractions at which the 2 pins intersected (expressed as a
372 /// ratio of 0 to 1 where 0 represents intersecting at the path outline
373 /// and 1 represents intersecting at the tip of the pin where the umbra
374 /// is darkest.
375 struct PinIntersection {
376 // The Point of the intersection between the pins.
377 Point intersection;
378 // The fraction along pin0 of the intersection.
379 Scalar fraction0;
380 // The fraction along pin1 of the intersection
381 Scalar fraction1;
382 };
383
384 /// Return the intersection between the 2 pins pin0 and pin1 if there
385 /// is an intersection, otherwise a nullopt to indicate that there is
386 /// no intersection.
387 static std::optional<PinIntersection> ComputeIntersection(UmbraPin& pin0,
388 UmbraPin& pin1);
389
390 /// Constants used to resolve pin intersections, adopted from the Skia
391 /// version of the algorithm.
392 static constexpr Scalar kCrossTolerance = 1.0f / 2048.0f;
393 static constexpr Scalar kIntersectionTolerance = 1.0e-6f;
394
395 /// Compute the squared length of a vector or a special out of bounds
396 /// value if the vector becomes infinite.
397 static constexpr Scalar FiniteVectorLengthSquared(Vector2 v) {
398 return !v.IsFinite() ? -1.0f : v.Dot(v);
399 }
400
401 /// Determine if the numerator and denominator are outside of the
402 /// interval that makes sense for an umbra intersection.
403 ///
404 /// Note calculation borrowed from Skia's SkPathUtils.
405 static constexpr inline bool OutsideInterval(Scalar numer,
406 Scalar denom,
407 bool denom_positive) {
408 return (denom_positive && (numer < 0 || numer > denom)) ||
409 (!denom_positive && (numer > 0 || numer < denom));
410 }
411
412 /// Remove the pin at p_pin from the linked list of pins when the caller
413 /// determines that it should not contribute to the final umbra polygon.
414 /// The pointer to the head pin at *p_head will also be adjusted if we've
415 /// eliminated the head pin itself and it will be additionally set to
416 /// nullptr if that was the last pin in the list.
417 ///
418 /// @param p_pin The pin to be eliminated from the list.
419 /// @param p_head The pointer to the head of the list which might also
420 /// need adjustment depending on which pin is removed.
421 static void RemovePin(UmbraPin* p_pin, UmbraPin** p_head);
422
423 /// A helper method for resolving pin conflicts, adopted directly from the
424 /// associated Skia algorithm.
425 ///
426 /// Note calculation borrowed from Skia's SkPathUtils.
427 static int ComputeSide(const Point& p0, const Vector2& v, const Point& p);
428
429 /// Run through the path calculating the outset vertices for the penumbra
430 /// and connecting them to the inset vertices of the umbra and then to
431 /// the centroid in a system of triangles with the appropriate alpha values
432 /// representing the intensity of the (non-gamma-adjusted) shadow at those
433 /// points. The resulting mesh should consist of 2 rings of triangles, an
434 /// inner ring connecting the centroid to the umbra polygon, and another
435 /// outer ring connecting vertices in the umbra polygon to vertices on the
436 /// outer edge of the penumbra.
437 ///
438 /// @param pins The list of pins, one for each edge of the polygon.
439 /// @param centroid The centroid ("center of mass") of the polygon.
440 /// @param list The linked list of the subset of pins that have
441 /// umbra vertices which appear in the umbra polygon.
442 /// @param trigs The vector of sin and cos for subdivided arcs that
443 /// can round the penumbra corner at each polygon corner.
444 /// @param direction The overall direction of the path as determined by
445 /// the consistent cross products of each edge turn.
446 void ComputeMesh(std::vector<UmbraPin>& pins,
447 const Point& centroid,
448 UmbraPinLinkedList& list,
449 const impeller::Tessellator::Trigs& trigs,
450 Scalar direction);
451
452 /// After the umbra_vertices of the pins are accumulated and linked into a
453 /// ring using their p_prev/p_next pointers, compute the best surviving umbra
454 /// vertex for each pin and set its location and index into the UmbraPin.
455 ///
456 /// @param pins The list of pins, one for each edge of the polygon.
457 /// @param list The linked list of the subset of pins that have
458 /// umbra vertices which appear in the umbra polygon.
459 /// @param centroid The centroid ("center of mass") of the polygon.
460 void PopulateUmbraVertices(std::vector<UmbraPin>& pins,
461 UmbraPinLinkedList& list,
462 const Point centroid);
463
464 /// Appends a fan of penumbra vertices centered on the path vertex of the
465 /// |p_curr_pin| starting from the absolute point |fan_start| and ending
466 /// at the absolute point |fan_end|, both of which should be equi-distant
467 /// from the path vertex. The index of the vertex at |fan_start| should
468 /// already be in the vector of vertices at an index given by |start_index|.
469 ///
470 /// @param p_curr_pin The pin at the corner around which the penumbra is
471 /// rotating.
472 /// @param fan_start The point on the penumbra where the fan starts.
473 /// @param fan_start The point on the penumbra where the fan ends.
474 /// @param start_index The index in the vector of vertices where the
475 /// fan_start vertex has already been inserted.
476 /// @param trigs The vector of sin and cos for subdivided arcs that
477 /// can round the penumbra corner at each polygon corner.
478 /// @param direction The overall direction of the path as determined by
479 /// the consistent cross products of each edge turn.
480 uint16_t AppendFan(const UmbraPin* p_curr_pin,
481 const Point& fan_start,
482 const Point& fan_end,
483 uint16_t start_index,
484 const impeller::Tessellator::Trigs& trigs,
485 Scalar direction);
486
487 /// Append a vertex and its associated gaussian coefficient to the lists
488 /// of vertices and guassians and return their (shared) index.
489 uint16_t AppendVertex(const Point& vertex, Scalar gaussian);
490
491 /// Append 3 indices to the indices vector to form a new triangle in the mesh.
492 void AddTriangle(uint16_t v0, uint16_t v1, uint16_t v2);
493};
494
495PolygonInfo::PolygonInfo(Scalar occluder_height)
496 : occluder_height_(occluder_height) {}
497
498const std::shared_ptr<ShadowVertices> PolygonInfo::CalculateConvexShadowMesh(
499 const impeller::PathSource& source,
500 const Matrix& matrix,
501 const Tessellator::Trigs& trigs) {
502 // We are operating in "2D device space" with respect to scale and there
503 // is no need to involve translations or Z or perspective in the various
504 // calculations, so we extract the incoming matrix's 2D scales on X and Y
505 // and just use those measurements to guide our tessellation.
506 Vector2 scale_2d = matrix.GetBasisScaleXY();
507
508 FML_DCHECK(scale_2d.x >= 0.0f && scale_2d.y >= 0.0f);
509 if (!(scale_2d.x * scale_2d.y > 0.0f)) {
510 // NaN should fall in here as well.
511 return ShadowVertices::kEmpty;
512 }
513
514 UmbraPinAccumulator pin_accumulator(scale_2d);
515
516 Scalar scale = std::max(scale_2d.x, scale_2d.y);
517 auto [point_count, contour_count] =
519 pin_accumulator.Reserve(point_count);
520
521 PathTessellator::PathToFilledVertices(source, pin_accumulator, scale);
522
523 switch (pin_accumulator.GetStatus()) {
524 case UmbraPinAccumulator::PathStatus::kEmpty:
525 return ShadowVertices::kEmpty;
526 case UmbraPinAccumulator::PathStatus::kNonConvex:
527 case UmbraPinAccumulator::PathStatus::kMultipleContours:
528 return nullptr;
529 case UmbraPinAccumulator::PathStatus::kConvex:
530 break;
531 }
532
533 std::vector<UmbraPin>& pins = pin_accumulator.GetPins();
534 const Point& centroid = pin_accumulator.GetCentroid();
535 Scalar direction = pin_accumulator.GetDirection();
536
537 ComputePinDirectionsAndMinDistanceToCentroid(pins, centroid, direction);
538
539 UmbraPinLinkedList list = ResolveUmbraIntersections(pins, direction);
540 if (list.IsNull()) {
541 // Ideally the Resolve algorithm will always be able to create an
542 // inner loop of umbra vertices, but it is not perfect.
543 //
544 // The Skia algorithm from which this was taken tries to fake an
545 // umbra polygon that is 95% from the path polygon to the centroid,
546 // but that result does not resemble a proper shadow. If we run into
547 // this case a lot we should either beef up the ResolveIntersections
548 // algorithm or find a better approximation than "95% to the centroid".
549 return nullptr;
550 }
551
552 ComputeMesh(pins, centroid, list, trigs, direction);
553
554 // Finally, we were working in a device space scaled by scale_2d
555 // so we need to un-scale the outgoing vertices by the same amount.
556 Vector2 inverted_scale_2d = 1.0f / scale_2d;
557 for (Point& vertex : vertices_) {
558 vertex = vertex * inverted_scale_2d;
559 }
560
561 return ShadowVertices::Make(std::move(vertices_), std::move(indices_),
562 std::move(gaussians_));
563}
564
565UmbraPinAccumulator::UmbraPinAccumulator(Vector2 device_scale)
566 : device_scale_(device_scale * kSubPixelCount) {}
567
568// Enter a new point for the polygon approximation of the shape. Points are
569// normalized to a device subpixel grid based on |kSubPixelCount|, duplicates
570// at that sub-pixel grid are ignored, collinear points are reduced to just
571// the endpoints, and the centroid is updated from the remaining non-duplicate
572// grid points.
573void UmbraPinAccumulator::Write(Point point) {
574 // This type of algorithm will never be able to handle multiple contours.
575 if (first_contour_ended_) {
576 has_multiple_contours_ = true;
577 return;
578 }
579 FML_DCHECK(!has_multiple_contours_);
580
581 point = ToDeviceGrid(point);
582
583 if (!pins_.empty()) {
584 // If this isn't the first point then we need to perform de-duplication
585 // and possibly convexity checking and centroid updates.
586 Point prev = pins_.back().path_vertex;
587
588 // Adjusted points are rounded so == testing is OK here even for floating
589 // point coordinates.
590 if (point == prev) {
591 // Ignore this point as a duplicate
592 return;
593 }
594
595 if (pins_.size() >= 2u) {
596 // A quick collinear check to avoid extra processing later.
597 Point prev_prev = pins_.end()[-2].path_vertex;
598 Vector2 v0 = prev - prev_prev;
599 Vector2 v1 = point - prev_prev;
600 Scalar cross = v0.Cross(v1);
601 if (cross == 0) {
602 // This point is on the same line as the line between the last
603 // 2 points, so skip the intermediate point. Points that are
604 // collinear only contribute to the edge of the shape the vector
605 // from the first to the last of them.
606 pins_.pop_back();
607 if (point == prev_prev) {
608 // Not only do we eliminate the previous point as collinear, but
609 // we also eliminate this point as a duplicate.
610 // This point would tend to be eliminated anyway because it would
611 // automatically be collinear with whatever the next point would
612 // be, but we just avoid inserting it anyway to reduce processing.
613 return;
614 }
615 }
616 }
617 }
618
619 pins_.emplace_back(point);
620}
621
622// Called at the end of every contour of which we hope there is only one.
623// If we detect more than one contour then the shadow tessellation becomes
624// invalid.
625//
626// Each contour will have exactly one point at the beginning and end which
627// are duplicates. The extra repeat of the first point actually helped the
628// centroid accumulation do its math for ever segment in the path, but
629// going forward we don't need the extra pin in the shape so we verify that
630// it is a duplicate and then we delete it.
631void UmbraPinAccumulator::EndContour() {
632 // This type of algorithm will never be able to handle multiple contours.
633 if (first_contour_ended_) {
634 has_multiple_contours_ = true;
635 return;
636 }
637 FML_DCHECK(!has_multiple_contours_);
638
639 // PathTessellator always ensures the path is closed back to the origin
640 // by an extra call to Write(Point).
641 FML_DCHECK(pins_.front().path_vertex == pins_.back().path_vertex);
642 pins_.pop_back();
643 first_contour_ended_ = true;
644}
645
646// Adjust the device point to its nearest sub-pixel grid location.
647Point UmbraPinAccumulator::ToDeviceGrid(Point point) {
648 return (point * device_scale_).Round() * kSubPixelScale;
649}
650
651// This method assumes that the pins have been accumulated by the PathVertex
652// methods which ensure that no adjacent points are identical or collinear.
653// It returns a PathResults that contains all of the relevant information
654// depending on the geometric state of the path itself (ignoring whether
655// the rest of the shadow processing will succeed).
656//
657// It performs 4 functions:
658// - Normalizes empty paths (either too few vertices, or no turning directin)
659// to an empty pins vector.
660// - Accumulates and sets the centroid of the path
661// - Accumulates and sets the overall direction of the path as determined by
662// the sign of the cross products which must all agree.
663// - Checks for convexity, including:
664// - The direction vector determined above.
665// - The turning direction of every triplet of points.
666// - The signs of the area accumulated using cross products.
667// - The number of times that the path edges change sign in X or Y.
668UmbraPinAccumulator::PathResults UmbraPinAccumulator::FinalizePath() {
669 FML_DCHECK(!results_.has_value());
670
671 if (has_multiple_contours_) {
672 return {.status = PathStatus::kMultipleContours};
673 }
674
675 if (pins_.size() < 3u) {
676 return {.status = PathStatus::kEmpty};
677 }
678
679 DirectionDetector x_direction_detector;
680 DirectionDetector y_direction_detector;
681
682 Point relative_centroid;
683 Scalar path_direction = 0.0f;
684 Scalar path_area = 0.0f;
685
686 Point prev = pins_.back().path_vertex;
687 Point prev_prev = pins_.end()[-2].path_vertex;
688 Point first = pins_.front().path_vertex;
689 for (UmbraPin& pin : pins_) {
690 Point new_point = pin.path_vertex;
691
692 // Check for going around more than once in the same direction.
693 {
694 Vector2 delta = new_point - prev;
695 x_direction_detector.AccumulateDirection(delta.x);
696 y_direction_detector.AccumulateDirection(delta.y);
697 if (x_direction_detector.IsConcave() ||
698 y_direction_detector.IsConcave()) {
699 return {.status = PathStatus::kNonConvex};
700 }
701 }
702
703 // Check if the path is locally convex over the most recent 3 vertices.
704 if (path_direction != 0.0f) {
705 Vector2 v0 = prev - prev_prev;
706 Vector2 v1 = new_point - prev_prev;
707 Scalar cross = v0.Cross(v1);
708 // We should have eliminated adjacent collinear points in the first pass.
709 FML_DCHECK(cross != 0.0f);
710 if (cross * path_direction < 0.0f) {
711 return {.status = PathStatus::kNonConvex};
712 }
713 }
714
715 // Check if the path is globally convex with respect to the first vertex.
716 {
717 Vector2 v0 = prev - first;
718 Vector2 v1 = new_point - first;
719 Scalar quad_area = v0.Cross(v1);
720 if (quad_area != 0) {
721 // convexity check for whole path which can detect if we turn more than
722 // 360 degrees and start going the other way wrt the start point, but
723 // does not detect if any pair of points are concave (checked above).
724 if (path_direction == 0) {
725 path_direction = std::copysign(1.0f, quad_area);
726 } else if (quad_area * path_direction < 0) {
727 return {.status = PathStatus::kNonConvex};
728 }
729
730 relative_centroid += (v0 + v1) * quad_area;
731 path_area += quad_area;
732 }
733 }
734
735 prev_prev = prev;
736 prev = new_point;
737 }
738
739 if (path_direction == 0.0f) {
740 // We never changed direction, indicate emptiness.
741 return {.status = PathStatus::kEmpty};
742 }
743
744 // We are computing the centroid using a weighted average of all of the
745 // centroids of the triangles in a tessellation of the polygon, in this
746 // case a triangle fan tessellation relative to the first point in the
747 // polygon. We could use any point, but since we had to compute the cross
748 // product above relative to the initial point in order to detect if the
749 // path turned more than once, we already have values available relative
750 // to that first point here.
751 //
752 // The centroid of each triangle is the 3-way average of the corners of
753 // that triangle. Since the triangles are all relative to the first point,
754 // one of those corners is (0, 0) in this relative triangle and so we can
755 // simply add up the x,y of the two relative points and divide by 3.0.
756 // Since all values in the sum are divided by 3.0, we can save that
757 // constant division until the end when we finalize the average computation.
758 //
759 // We also weight these centroids by the area of the triangle so that we
760 // adjust for the parts of the polygon that are represented more densely
761 // and the parts that span a larger part of its circumference. A simple
762 // average would bias the centroid towards parts of the polygon where the
763 // points are denser. If we are rendering a polygonal representation of
764 // a round rect with only one round corner, all of the many approximating
765 // segments of the flattened round corner would overwhelm the handful of
766 // other simple segments for the flat sides. A weighted average places the
767 // centroid back at the "center of mass" of the polygon.
768 //
769 // Luckily, the same cross product used above that helps us determine the
770 // turning and convexity of the polygon also provides us with the area of
771 // the parallelogram projected from the 3 points in the triangle. That
772 // area is exactly double the area of the triangle itself. We could divide
773 // by 2 here, but since we are also accumulating these cross product values
774 // for the final weighted division, the factors of 2 all cancel out.
775 //
776 // path_area is (2 * triangle area).
777 // relative_centroid is accumulating sum(3 * triangle centroid * quad area).
778 // path_area_ is accumulating sum(quad area).
779 //
780 // The final combined average weight factor will be (3 * sum(quad area)).
781 relative_centroid /= 3.0f * path_area;
782
783 // The centroid accumulation was relative to the first point in the
784 // polygon so we make it absolute here.
785 return {
786 .status = PathStatus::kConvex,
787 .centroid = pins_[0].path_vertex + relative_centroid,
788 .path_direction = path_direction,
789 };
790}
791
792void PolygonInfo::ComputePinDirectionsAndMinDistanceToCentroid(
793 std::vector<UmbraPin>& pins,
794 const Point& centroid,
795 Scalar direction) {
796 Scalar desired_umbra_size = GetUmbraSizeForHeight(occluder_height_);
797 Scalar min_umbra_squared = desired_umbra_size * desired_umbra_size;
798 FML_DCHECK(direction == 1.0f || direction == -1.0f);
799
800 // For simplicity of iteration, we start with the last vertex as the
801 // "previous" pin and then iterate once over the vector of pins,
802 // performing these calculations on the path segment from the previous
803 // pin to the current pin. In the end, all pins and therefore all path
804 // segments are processed once even if we start with the last pin.
805
806 // First pass, compute the smallest distance to the centroid.
807 UmbraPin* p_prev_pin = &pins.back();
808 for (UmbraPin& pin : pins) {
809 UmbraPin* p_curr_pin = &pin;
810
811 // Accumulate (min) the distance from the centroid to "this" segment.
812 Scalar distance_squared = centroid.GetDistanceToSegmentSquared(
813 p_prev_pin->path_vertex, p_curr_pin->path_vertex);
814 min_umbra_squared = std::min(min_umbra_squared, distance_squared);
815
816 p_prev_pin = p_curr_pin;
817 }
818
819 static constexpr auto kTolerance = 1.0e-2f;
820 Scalar umbra_size = std::sqrt(min_umbra_squared);
821 if (umbra_size < desired_umbra_size + kTolerance) {
822 // if the umbra would collapse, we back off a bit on the inner blur and
823 // adjust the alpha
824 auto newInset = umbra_size - kTolerance;
825 auto ratio = 0.5f * (newInset / desired_umbra_size + 1);
826 FML_DCHECK(std::isfinite(ratio));
827
828 umbra_gaussian_ = ratio;
829 umbra_size = newInset;
830 } else {
831 FML_DCHECK(umbra_gaussian_ == 1.0f);
832 }
833
834 // Second pass, fill out the pin data with the final umbra size.
835 //
836 // We also link all of the pins into a circular linked list so they can be
837 // quickly eliminated in the method that resolves intersections of the pins.
838 Scalar penumbra_scale = -GetPenumbraSizeForHeight(occluder_height_);
839 p_prev_pin = &pins.back();
840 for (UmbraPin& pin : pins) {
841 UmbraPin* p_curr_pin = &pin;
842 p_curr_pin->p_prev = p_prev_pin;
843 p_prev_pin->p_next = p_curr_pin;
844
845 // We compute the vector along the path segment from the previous
846 // path vertex to this one as well as the unit direction vector
847 // that points from that pin towards the center of the shape,
848 // perpendicular to that segment.
849 p_prev_pin->path_delta = p_curr_pin->path_vertex - p_prev_pin->path_vertex;
850 Vector2 pin_direction = p_prev_pin
851 ->path_delta //
852 .Normalize()
853 .PerpendicularRight() *
854 direction;
855
856 p_prev_pin->penumbra_delta = pin_direction * penumbra_scale;
857 p_prev_pin->umbra_vertex = //
858 p_prev_pin->pin_tip =
859 p_prev_pin->path_vertex + pin_direction * umbra_size;
860
861 p_prev_pin = p_curr_pin;
862 }
863}
864
865// Compute the intersection 'p' between the two pins pin 0 and pin 1, if any.
866// The intersection structure will contain the fractional distances along the
867// pins of the intersection and the intersection point itself if there is an
868// intersection.
869//
870// The intersection structure will be reset to empty otherwise.
871//
872// This method was converted nearly verbatim from the Skia source files
873// SkShadowTessellator.cpp and SkPolyUtils.cpp, except for variable
874// naming and differences in the methods on Point and Vertex2.
875std::optional<PolygonInfo::PinIntersection> PolygonInfo::ComputeIntersection(
876 UmbraPin& pin0,
877 UmbraPin& pin1) {
878 Vector2 v0 = pin0.path_delta;
879 Vector2 v1 = pin1.path_delta;
880 Vector2 tip_delta = pin1.pin_tip - pin0.pin_tip;
881 Vector2 w = tip_delta;
882 Scalar denom = pin0.path_delta.Cross(pin1.path_delta);
883 bool denom_positive = (denom > 0);
884 Scalar numerator0, numerator1;
885
886 if (ScalarNearlyZero(denom, kCrossTolerance)) {
887 // This code also exists in the Skia version of this method, but it is
888 // not clear that we can ever enter here. In particular, since points
889 // were normalized to a grid (1/16th of a pixel), de-duplicated, and
890 // collinear points eliminated, denom can never be 0. And since the
891 // denom value was computed from a cross product of non-normalized
892 // delta vectors, its magnitude must exceed 1/256 which is far greater
893 // than the tolerance value.
894 //
895 // Note that in the Skia code, this method lived in a general polygon
896 // module that was unaware that it was being fed de-duplicated vertices
897 // from the Shadow module, so this code might be possible to trigger
898 // for "unfiltered" polygons, but not the normalized polygons that our
899 // (and Skia's) shadow code uses.
900 //
901 // Though entering here seems unlikely, we include the code until we can
902 // perform more due diligence in vetting that this is truly dead code.
903
904 // segments are parallel, but not collinear
905 if (!ScalarNearlyZero(tip_delta.Cross(pin0.path_delta), kCrossTolerance) ||
906 !ScalarNearlyZero(tip_delta.Cross(pin1.path_delta), kCrossTolerance)) {
907 return std::nullopt;
908 }
909
910 // Check for zero-length segments
911 Scalar v0_length_squared = FiniteVectorLengthSquared(v0);
912 if (v0_length_squared <= 0.0f) {
913 // Both are zero-length
914 Scalar v1_length_squared = FiniteVectorLengthSquared(v1);
915 if (v1_length_squared <= 0.0f) {
916 // Check if they're the same point
917 if (w.IsFinite() && !w.IsZero()) {
918 return {{
919 .intersection = pin0.pin_tip,
920 .fraction0 = 0.0f,
921 .fraction1 = 0.0f,
922 }};
923 } else {
924 // Intersection is indeterminate
925 return std::nullopt;
926 }
927 }
928 // Otherwise project segment0's origin onto segment1
929 numerator1 = v1.Dot(-w);
930 denom = v1_length_squared;
931 if (OutsideInterval(numerator1, denom, true)) {
932 return std::nullopt;
933 }
934 numerator0 = 0;
935 } else {
936 // Project segment1's endpoints onto segment0
937 numerator0 = v0.Dot(w);
938 denom = v0_length_squared;
939 numerator1 = 0;
940 if (OutsideInterval(numerator0, denom, true)) {
941 // The first endpoint doesn't lie on segment0
942 // If segment1 is degenerate, then there's no collision
943 Scalar v1_length_squared = FiniteVectorLengthSquared(v1);
944 if (v1_length_squared <= 0.0f) {
945 return std::nullopt;
946 }
947
948 // Otherwise try the other one
949 Scalar old_numerator0 = numerator0;
950 numerator0 = v0.Dot(w + v1);
951 numerator1 = denom;
952 if (OutsideInterval(numerator0, denom, true)) {
953 // it's possible that segment1's interval surrounds segment0
954 // this is false if params have the same signs, and in that case
955 // no collision
956 if (numerator0 * old_numerator0 > 0) {
957 return std::nullopt;
958 }
959 // otherwise project segment0's endpoint onto segment1 instead
960 numerator0 = 0;
961 numerator1 = v1.Dot(-w);
962 denom = v1_length_squared;
963 }
964 }
965 }
966 } else {
967 numerator0 = w.Cross(v1);
968 if (OutsideInterval(numerator0, denom, denom_positive)) {
969 return std::nullopt;
970 }
971 numerator1 = w.Cross(v0);
972 if (OutsideInterval(numerator1, denom, denom_positive)) {
973 return std::nullopt;
974 }
975 }
976
977 Scalar fraction0 = numerator0 / denom;
978 Scalar fraction1 = numerator1 / denom;
979
980 return {{
981 .intersection = pin0.pin_tip + v0 * fraction0,
982 .fraction0 = fraction0,
983 .fraction1 = fraction1,
984 }};
985}
986
987void PolygonInfo::RemovePin(UmbraPin* p_pin, UmbraPin** p_head) {
988 UmbraPin* p_next = p_pin->p_next;
989 UmbraPin* p_prev = p_pin->p_prev;
990 p_prev->p_next = p_next;
991 p_next->p_prev = p_prev;
992 if (*p_head == p_pin) {
993 *p_head = (p_next == p_pin) ? nullptr : p_next;
994 }
995}
996
997// Computes the relative direction for point p compared to segment defined
998// by origin p0 and vector v. A positive value means the point is to the
999// left of the segment, negative is to the right, 0 is collinear.
1000int PolygonInfo::ComputeSide(const Point& p0,
1001 const Vector2& v,
1002 const Point& p) {
1003 Vector2 w = p - p0;
1004 Scalar cross = v.Cross(w);
1005 if (!impeller::ScalarNearlyZero(cross, kCrossTolerance)) {
1006 return ((cross > 0) ? 1 : -1);
1007 }
1008
1009 return 0;
1010}
1011
1012// This method was converted nearly verbatim from the Skia source files
1013// SkShadowTessellator.cpp and SkPolyUtils.cpp, except for variable
1014// naming and differences in the methods on Point and Vertex2.
1015PolygonInfo::UmbraPinLinkedList PolygonInfo::ResolveUmbraIntersections(
1016 std::vector<UmbraPin>& pins,
1017 Scalar direction) {
1018 UmbraPin* p_head_pin = &pins.front();
1019 UmbraPin* p_curr_pin = p_head_pin;
1020 UmbraPin* p_prev_pin = p_curr_pin->p_prev;
1021 size_t umbra_vertex_count = pins.size();
1022
1023 // we should check each edge against each other edge at most once
1024 size_t allowed_iterations = pins.size() * pins.size() + 1u;
1025
1026 while (p_head_pin && p_prev_pin != p_curr_pin) {
1027 if (--allowed_iterations == 0) {
1028 return {};
1029 }
1030
1031 std::optional<PinIntersection> intersection =
1032 ComputeIntersection(*p_prev_pin, *p_curr_pin);
1033 if (intersection.has_value()) {
1034 // If the new intersection is further back on previous inset from the
1035 // prior intersection...
1036 if (intersection->fraction0 < p_prev_pin->umbra_fraction) {
1037 // no point in considering this one again
1038 RemovePin(p_prev_pin, &p_head_pin);
1039 --umbra_vertex_count;
1040 // go back one segment
1041 p_prev_pin = p_prev_pin->p_prev;
1042 } else if (p_curr_pin->IsFractionInitialized() &&
1043 p_curr_pin->umbra_vertex.GetDistanceSquared(
1044 intersection->intersection) < kIntersectionTolerance) {
1045 // We've already considered this intersection and come to the same
1046 // result, we're done.
1047 break;
1048 } else {
1049 // Add intersection.
1050 p_curr_pin->umbra_vertex = intersection->intersection;
1051 p_curr_pin->umbra_fraction = intersection->fraction1;
1052
1053 // go to next segment
1054 p_prev_pin = p_curr_pin;
1055 p_curr_pin = p_curr_pin->p_next;
1056 }
1057 } else {
1058 // if previous pin is to right side of the current pin...
1059 int side = direction * ComputeSide(p_curr_pin->pin_tip, //
1060 p_curr_pin->path_delta, //
1061 p_prev_pin->pin_tip);
1062 if (side < 0 &&
1063 side == direction * ComputeSide(p_curr_pin->pin_tip, //
1064 p_curr_pin->path_delta, //
1065 p_prev_pin->pin_tip +
1066 p_prev_pin->path_delta)) {
1067 // no point in considering this one again
1068 RemovePin(p_prev_pin, &p_head_pin);
1069 --umbra_vertex_count;
1070 // go back one segment
1071 p_prev_pin = p_prev_pin->p_prev;
1072 } else {
1073 // move to next segment
1074 RemovePin(p_curr_pin, &p_head_pin);
1075 --umbra_vertex_count;
1076 p_curr_pin = p_curr_pin->p_next;
1077 }
1078 }
1079 }
1080
1081 if (!p_head_pin) {
1082 return {};
1083 }
1084
1085 // Now remove any duplicates from the umbra polygon. The head pin is
1086 // automatically included as the first point of the umbra polygon.
1087 p_prev_pin = p_head_pin;
1088 p_curr_pin = p_head_pin->p_next;
1089 size_t umbra_vertices = 1u;
1090 while (p_curr_pin != p_head_pin) {
1091 if (p_prev_pin->umbra_vertex.GetDistanceSquared(p_curr_pin->umbra_vertex) <
1092 kMinSubPixelDistanceSquared) {
1093 RemovePin(p_curr_pin, &p_head_pin);
1094 p_curr_pin = p_curr_pin->p_next;
1095 } else {
1096 umbra_vertices++;
1097 p_prev_pin = p_curr_pin;
1098 p_curr_pin = p_curr_pin->p_next;
1099 }
1100 FML_DCHECK(p_curr_pin == p_prev_pin->p_next);
1101 FML_DCHECK(p_prev_pin == p_curr_pin->p_prev);
1102 }
1103
1104 if (umbra_vertices < 3u) {
1105 return {};
1106 }
1107
1108 return {p_head_pin, umbra_vertices};
1109}
1110
1111// The mesh computed connects all of the points in two rings. The outermost
1112// ring represents the point where the shadow disappears and those points
1113// are associated with an alpha of 0. The umbra polygon represents the ring
1114// where the shadow is its darkest, usually fully "opaque" (potentially
1115// modulated by a non-opaque shadow color, but opaque with respect to the
1116// shadow's varying intensity). The umbra polygon may not be fully "opaque"
1117// with respect to the shadow cast by the shape if the shadows radius is
1118// larger than the cross-section of the shape. If the umbra polygon is pulled
1119// back from extending the shadow distance inward due to this phenomenon,
1120// then the umbra_gaussian will be computed to be less than fully opaque.
1121//
1122// The mesh will connect the centroid to the umbra (inner) polygon at a
1123// constant level as computed in umbra_gaussian, and then the umbra polygon
1124// is connected to the nearest points on the penumbra (outer) polygon which
1125// is seeded with points that are fully transparent (umbra level 0).
1126//
1127// This creates 2 rings of triangles that are interspersed in the vertices_
1128// and connected into triangles using indices_ both to reuse the vertices
1129// as best we can and also because we don't generate the vertices in any
1130// kind of useful fan or strip format. The points are reused as such:
1131//
1132// - The centroid vertex will be used once for each pair of umbra vertices
1133// to make triangles for the inner ring.
1134// - Each umbra vertex will be used in both the inner and the outer rings.
1135// In particular, in 2 of the inner ring triangles and in an arbitrary
1136// number of the outer ring vertices (each outer ring vertex is connected
1137// to the neariest inner ring vertex so the mapping is not predictable).
1138// - Each outer ring vertex is used in at least 2 outer ring triangles, the
1139// one that links to the vertex before it and the one that links to the
1140// vertex following it, plus we insert extra vertices on the outer ring
1141// to turn the corners beteween the projected segments.
1142void PolygonInfo::ComputeMesh(std::vector<UmbraPin>& pins,
1143 const Point& centroid,
1144 UmbraPinLinkedList& list,
1145 const impeller::Tessellator::Trigs& trigs,
1146 Scalar direction) {
1147 // Centroid and umbra polygon...
1148 size_t vertex_count = list.pin_count + 1u;
1149 size_t triangle_count = list.pin_count;
1150
1151 // Penumbra corners - likely many more fan vertices than estimated...
1152 size_t penumbra_count = pins.size() * 2; // 2 perp at each vertex.
1153 penumbra_count += trigs.size() * 4; // total 360 degrees of fans.
1154 vertex_count += penumbra_count;
1155 triangle_count += penumbra_count;
1156
1157 vertices_.reserve(vertex_count);
1158 gaussians_.reserve(vertex_count);
1159 indices_.reserve(triangle_count * 3);
1160
1161 // First we populate the umbra_vertex and umbra_index of each pin with its
1162 // nearest point on the umbra polygon (the linked list computed earlier).
1163 //
1164 // This step simplifies the following operations because we will always
1165 // know which umbra vertex each pin object is associated with and whether
1166 // we need to bridge between them as we progress through the pins, without
1167 // having to search through the linked list every time.
1168 //
1169 // This method will also fill in the inner part of the mesh that connects
1170 // the centroid to every vertex in the umbra polygon with triangles that
1171 // are all at the maximum umbra gaussian coefficient.
1172 PopulateUmbraVertices(pins, list, centroid);
1173
1174 // We now run through the list of all pins and append points and triangles
1175 // to our internal vectors to cover the part of the mesh that extends
1176 // out from the umbra polygon to the outer penumbra points.
1177 //
1178 // Each pin assumes that the previous pin contributed some points to the
1179 // penumbra polygon that ended with the point that is perpendicular to
1180 // the side between that previous path vertex and its own path vertex.
1181 // This pin will then contribute any number of the following points to
1182 // the penumbra polygon:
1183 //
1184 // - If this pin uses a different umbra vertex than the previous pin
1185 // (common for simple large polygons that have no clipping of their
1186 // inner umbra points) then it inserts a bridging quad that connects
1187 // from the ending segment of the previous pin to the starting segment
1188 // of this pin. If both are based on the same umbra vertex then the
1189 // end of the previous pin is identical to the start of this one.
1190 // - Possibly a fan of extra vertices to round the corner from the
1191 // last segment added, which is perpendicular to the previous path
1192 // segment, to the final segmet of this pin, which will be perpendicular
1193 // to the following path segment.
1194 // - The last penumbra point added will be the penumbra point that is
1195 // perpendicular to the following segment, which prepares for the
1196 // initial conditions that the next pin will expect.
1197 const UmbraPin* p_prev_pin = &pins.back();
1198
1199 // This point may be duplicated at the end of the path. We can try to
1200 // avoid adding it twice with some bookkeeping, but it is simpler to
1201 // just add it here for the pre-conditions of the start of the first
1202 // pin and allow the duplication to happen naturally as we process the
1203 // final pin later. One extra point should not be very noticeable in
1204 // the long list of mesh vertices.
1205 Point last_penumbra_point =
1206 p_prev_pin->path_vertex + p_prev_pin->penumbra_delta;
1207 uint16_t last_penumbra_index = AppendVertex(last_penumbra_point, 0.0f);
1208
1209 for (const UmbraPin& pin : pins) {
1210 const UmbraPin* p_curr_pin = &pin;
1211
1212 // Preconditions:
1213 // - last_penumbra_point was the last outer vertex added by the
1214 // previous pin
1215 // - last_penumbra_index is its index in the vertices to be used
1216 // for creating indexed triangles.
1217
1218 if (p_prev_pin->umbra_index != p_curr_pin->umbra_index) {
1219 // We've moved on to a new umbra index to anchor our penumbra triangles.
1220 // We need to bridge the gap so that we are now building a new fan from
1221 // a point that has the same relative angle from the current pin's
1222 // path vertex as the previous penumbra point had from the previous
1223 // pin's path vertex.
1224 //
1225 // Our previous penumbra fan vector would have gone from the previous
1226 // pin's umbra point to the previous pen's final penumbra point:
1227 // - prev->umbra_vertex
1228 // => prev->path_vertex + prev->penumbra_delta
1229 // We will connect to a parallel vector that extends from the new
1230 // (current pin's) umbra index in the same direction:
1231 // - curr->umbra_vertex
1232 // => curr->path_vertex + prev->penumbra_delta
1233
1234 // First we pivot about the old penumbra point to bridge from the old
1235 // umbra vertex to our new umbra point.
1236 AddTriangle(last_penumbra_index, //
1237 p_prev_pin->umbra_index, p_curr_pin->umbra_index);
1238 }
1239
1240 // Then we bridge from the old penumbra point to the new parallel
1241 // penumbra point, pivoting around the new umbra index.
1242 Point new_penumbra_point =
1243 p_curr_pin->path_vertex + p_prev_pin->penumbra_delta;
1244 uint16_t new_penumbra_index = AppendVertex(new_penumbra_point, 0.0f);
1245
1246 if (last_penumbra_index != new_penumbra_index) {
1247 AddTriangle(p_curr_pin->umbra_index, last_penumbra_index,
1248 new_penumbra_index);
1249 }
1250
1251 last_penumbra_point = new_penumbra_point;
1252 last_penumbra_index = new_penumbra_index;
1253
1254 // Now draw a fan from the current pin's umbra vertex to all of the
1255 // penumbra points associated with this pin's path vertex, ending at
1256 // our new final penumbra point associated with this pin.
1257 new_penumbra_point = p_curr_pin->path_vertex + p_curr_pin->penumbra_delta;
1258 new_penumbra_index =
1259 AppendFan(p_curr_pin, last_penumbra_point, new_penumbra_point,
1260 last_penumbra_index, trigs, direction);
1261
1262 last_penumbra_point = new_penumbra_point;
1263 last_penumbra_index = new_penumbra_index;
1264 p_prev_pin = p_curr_pin;
1265 }
1266}
1267
1268// Visit each pin and find the nearest umbra_vertex from the linked list of
1269// surviving umbra pins so we don't have to constantly find this as we stitch
1270// together the mesh.
1271void PolygonInfo::PopulateUmbraVertices(std::vector<UmbraPin>& pins,
1272 UmbraPinLinkedList& list,
1273 const Point centroid) {
1274 // We should be having the first crack at the vertex list, filling it with
1275 // the centroid, the umbra vertices, and the mesh connecting those into the
1276 // central core of the shadow.
1277 FML_DCHECK(list.p_head_pin != nullptr);
1278 FML_DCHECK(vertices_.empty());
1279 FML_DCHECK(gaussians_.empty());
1280 FML_DCHECK(indices_.empty());
1281
1282 // Always start with the centroid.
1283 uint16_t last_umbra_index = AppendVertex(centroid, umbra_gaussian_);
1284 FML_DCHECK(last_umbra_index == 0u);
1285
1286 // curr_umbra_pin is the most recently matched umbra vertex pin.
1287 // next_umbra_pin is the next umbra vertex pin to consider.
1288 // These pointers will always point to one of the pins that is on the
1289 // linked list of surviving umbra pins, possibly jumping over many
1290 // other umbra pins that were eliminated when we inset the polygon.
1291 UmbraPin* p_next_umbra_pin = list.p_head_pin;
1292 UmbraPin* p_curr_umbra_pin = p_next_umbra_pin->p_prev;
1293 for (UmbraPin& pin : pins) {
1294 if (p_next_umbra_pin == &pin ||
1295 (pin.path_vertex.GetDistanceSquared(p_curr_umbra_pin->umbra_vertex) >
1296 pin.path_vertex.GetDistanceSquared(p_next_umbra_pin->umbra_vertex))) {
1297 // We always bump to the next vertex when it was generated from this
1298 // pin, and also when it is closer to this path_vertex than the last
1299 // matched pin (curr).
1300 p_curr_umbra_pin = p_next_umbra_pin;
1301 p_next_umbra_pin = p_next_umbra_pin->p_next;
1302
1303 // New umbra vertex - append it and remember its index.
1304 uint16_t new_umbra_index =
1305 AppendVertex(p_curr_umbra_pin->umbra_vertex, umbra_gaussian_);
1306 p_curr_umbra_pin->umbra_index = new_umbra_index;
1307 if (last_umbra_index != 0u) {
1308 AddTriangle(0u, last_umbra_index, new_umbra_index);
1309 }
1310 last_umbra_index = new_umbra_index;
1311 }
1312 if (p_curr_umbra_pin != &pin) {
1313 pin.umbra_vertex = p_curr_umbra_pin->umbra_vertex;
1314 pin.umbra_index = last_umbra_index;
1315 }
1316 FML_DCHECK(pin.umbra_index != 0u);
1317 }
1318 if (last_umbra_index != pins.front().umbra_index) {
1319 AddTriangle(0u, last_umbra_index, pins.front().umbra_index);
1320 }
1321}
1322
1323// Appends a fan based on center from the relative point in start_delta to
1324// the relative point in end_delta, potentially adding additional relative
1325// vectors if the turning rate is faster than the trig values in trigs_.
1326uint16_t PolygonInfo::AppendFan(const UmbraPin* p_curr_pin,
1327 const Vector2& start,
1328 const Vector2& end,
1329 uint16_t start_index,
1330 const impeller::Tessellator::Trigs& trigs,
1331 Scalar direction) {
1332 Point center = p_curr_pin->path_vertex;
1333 uint16_t center_index = p_curr_pin->umbra_index;
1334 uint16_t prev_index = start_index;
1335
1336 Vector2 start_delta = start - center;
1337 Vector2 end_delta = end - center;
1338 size_t trig_count = trigs.size();
1339 for (size_t i = 1u; i < trig_count; i++) {
1340 Trig trig = trigs[i];
1341 Point fan_delta = (direction >= 0 ? trig : -trig) * start_delta;
1342 if (fan_delta.Cross(end_delta) * direction <= 0) {
1343 break;
1344 }
1345 uint16_t cur_index = AppendVertex(center + fan_delta, 0.0f);
1346 if (prev_index != cur_index) {
1347 AddTriangle(center_index, prev_index, cur_index);
1348 prev_index = cur_index;
1349 }
1350 if (i == trig_count - 1) {
1351 // This corner was >90 degrees so we start the loop over in case there
1352 // are more intermediate angles to emit.
1353 //
1354 // We set the loop variable to 0u which looks like it might apply a
1355 // 0 rotation to the new start_delta, but the for loop is about to
1356 // auto-incrment the variable to 1u, which will start at the next
1357 // non-0 rotation angle.
1358 i = 0u;
1359 start_delta = fan_delta;
1360 }
1361 }
1362 uint16_t cur_index = AppendVertex(center + end_delta, 0.0f);
1363 if (prev_index != cur_index) {
1364 AddTriangle(center_index, prev_index, cur_index);
1365 }
1366 return cur_index;
1367}
1368
1369// Appends a vertex and gaussian value into the associated std::vectors
1370// and returns the index at which the point was inserted.
1371uint16_t PolygonInfo::AppendVertex(const Point& vertex, Scalar gaussian) {
1372 FML_DCHECK(gaussian >= 0.0f && gaussian <= 1.0f);
1373 uint16_t index = vertices_.size();
1374 FML_DCHECK(index == gaussians_.size());
1375 // TODO(jimgraham): Turn this condition into a failure of the tessellation
1376 FML_DCHECK(index <= std::numeric_limits<uint16_t>::max());
1377 if (index > 0u) {
1378 FML_DCHECK(!gaussians_.empty() && !vertices_.empty());
1379 if (gaussian == gaussians_.back() && vertex == vertices_.back()) {
1380 return index - 1;
1381 }
1382 }
1383 vertices_.push_back(vertex);
1384 gaussians_.push_back(gaussian);
1385 return index;
1386}
1387
1388// Appends a triangle of the 3 indices into the indices_ vector.
1389void PolygonInfo::AddTriangle(uint16_t v0, uint16_t v1, uint16_t v2) {
1390 FML_DCHECK(std::max(std::max(v0, v1), v2) < vertices_.size());
1391 indices_.push_back(v0);
1392 indices_.push_back(v1);
1393 indices_.push_back(v2);
1394}
1395
1396} // namespace
1397
1398namespace impeller {
1399
1400const std::shared_ptr<ShadowVertices> ShadowVertices::kEmpty =
1401 std::make_shared<ShadowVertices>();
1402
1403std::optional<Rect> ShadowVertices::GetBounds() const {
1404 return Rect::MakePointBounds(vertices_);
1405}
1406
1408 const Matrix& matrix,
1409 const PathSource& source,
1410 Scalar occluder_height)
1411 : shadow_vertices_(MakeAmbientShadowVertices(tessellator,
1412 source,
1413 occluder_height,
1414 matrix)) {}
1415
1417 return shadow_vertices_ != nullptr;
1418}
1419
1421 return shadow_vertices_ != nullptr && shadow_vertices_->IsEmpty();
1422}
1423
1424const std::shared_ptr<ShadowVertices>& ShadowPathGeometry::GetShadowVertices()
1425 const {
1426 return shadow_vertices_;
1427}
1428
1429const std::shared_ptr<ShadowVertices> ShadowPathGeometry::TakeShadowVertices() {
1430 return std::move(shadow_vertices_);
1431}
1432
1434 const Entity& entity,
1435 RenderPass& pass) const {
1436 using VS = ShadowVerticesVertexShader;
1437
1438 size_t vertex_count = GetVertexCount();
1439
1440 BufferView vertex_buffer = renderer.GetTransientsDataBuffer().Emplace(
1441 vertex_count * sizeof(VS::PerVertexData), alignof(VS::PerVertexData),
1442 [&](uint8_t* data) {
1443 VS::PerVertexData* vtx_contents =
1444 reinterpret_cast<VS::PerVertexData*>(data);
1445 for (size_t i = 0u; i < vertex_count; i++) {
1446 vtx_contents[i] = {
1447 .position = vertices_[i],
1448 .gaussian = gaussians_[i],
1449 };
1450 }
1451 });
1452
1453 size_t index_count = GetIndexCount();
1454 const uint16_t* indices_data = GetIndices().data();
1455 BufferView index_buffer = {};
1456 index_buffer = renderer.GetTransientsIndexesBuffer().Emplace(
1457 indices_data, index_count * sizeof(uint16_t), alignof(uint16_t));
1458
1459 return GeometryResult{
1461 .vertex_buffer =
1462 {
1463 .vertex_buffer = vertex_buffer,
1464 .index_buffer = index_buffer,
1465 .vertex_count = index_count,
1466 .index_type = IndexType::k16bit,
1467 },
1468 .transform = entity.GetShaderTransform(pass),
1469 };
1470}
1471
1473 Tessellator& tessellator,
1474 const PathSource& source,
1475 Scalar occluder_height,
1476 const Matrix& matrix) {
1477 Scalar trig_radius = PolygonInfo::GetTrigRadiusForHeight(occluder_height);
1478 Tessellator::Trigs trigs = tessellator.GetTrigsForDeviceRadius(trig_radius);
1479
1480 PolygonInfo polygon(occluder_height);
1481
1482 return polygon.CalculateConvexShadowMesh(source, matrix, trigs);
1483}
1484
1485} // namespace impeller
HostBuffer & GetTransientsDataBuffer() const
Retrieve the current host buffer for transient storage of other non-index data.
HostBuffer & GetTransientsIndexesBuffer() const
Retrieve the current host buffer for transient storage of indexes used for indexed draws.
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
static std::pair< size_t, size_t > CountFillStorage(const PathSource &source, Scalar scale)
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition render_pass.h:30
const std::shared_ptr< ShadowVertices > & GetShadowVertices() const
ShadowPathGeometry(Tessellator &tessellator, const Matrix &matrix, const PathSource &source, Scalar occluder_height)
bool IsEmpty() const
Returns true if this shadow has no effect, is not visible.
const std::shared_ptr< ShadowVertices > TakeShadowVertices()
static std::shared_ptr< ShadowVertices > MakeAmbientShadowVertices(Tessellator &tessellator, const PathSource &source, Scalar occluder_height, const Matrix &matrix)
std::optional< Rect > GetBounds() const
static const std::shared_ptr< ShadowVertices > kEmpty
const std::vector< uint16_t > & GetIndices() const
GeometryResult GetPositionBuffer(const ContentContext &renderer, const Entity &entity, RenderPass &pass) const
size_t GetIndexCount() const
The count of the indices that define the mesh.
A utility that generates triangles of the specified fill type given a polyline. This happens on the C...
Definition tessellator.h:37
Trigs GetTrigsForDeviceRadius(Scalar pixel_radius)
#define FML_DCHECK(condition)
Definition logging.h:122
Point Vector2
Definition point.h:430
float Scalar
Definition scalar.h:19
constexpr float kEhCloseEnough
Definition constants.h:57
constexpr bool ScalarNearlyZero(Scalar x, Scalar tolerance=kEhCloseEnough)
Definition scalar.h:31
TPoint< Scalar > Point
Definition point.h:426
LinePipeline::VertexShader VS
PrimitiveType type
Definition geometry.h:37
A 4x4 matrix using column-major storage.
Definition matrix.h:37
static constexpr TPoint Round(const TPoint< U > &other)
Definition point.h:50
static constexpr std::optional< TRect > MakePointBounds(const U &value)
Definition rect.h:189
A structure to store the sine and cosine of an angle.
Definition trig.h:16
const size_t start
const size_t end