blob: 79f8f22636fee38715db4176552fb277647a8e73 [file] [log] [blame]
Jim Van Verthbce74962017-01-25 09:39:46 -05001/*
2 * Copyright 2017 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
Jim Van Verthefe3ded2017-01-30 13:11:45 -05008#include "SkShadowTessellator.h"
Cary Clarka4083c92017-09-15 11:59:23 -04009#include "SkColorData.h"
Jim Van Verth1af03d42017-07-31 09:34:58 -040010#include "SkDrawShadowInfo.h"
Jim Van Verth58abc9e2017-01-25 11:05:01 -050011#include "SkGeometry.h"
Jim Van Verth41964ed2018-03-28 10:10:30 -040012#include "SkOffsetPolygon.h"
Jim Van Verth85dc96b2017-01-30 14:49:21 -050013#include "SkPath.h"
Mike Reed54518ac2017-07-22 08:29:48 -040014#include "SkPoint3.h"
Cary Clarkdf429f32017-11-08 11:44:31 -050015#include "SkPointPriv.h"
Brian Salomonaff27a22017-02-06 15:47:44 -050016#include "SkVertices.h"
Jim Van Verth85dc96b2017-01-30 14:49:21 -050017
18#if SK_SUPPORT_GPU
19#include "GrPathUtils.h"
20#endif
Jim Van Verth58abc9e2017-01-25 11:05:01 -050021
Brian Salomon958fbc42017-01-30 17:01:28 -050022
Jim Van Vertha84898d2017-02-06 13:38:23 -050023/**
24 * Base class
25 */
Brian Salomonaff27a22017-02-06 15:47:44 -050026class SkBaseShadowTessellator {
Brian Salomon958fbc42017-01-30 17:01:28 -050027public:
Jim Van Verthe308a122017-05-08 14:19:30 -040028 SkBaseShadowTessellator(const SkPoint3& zPlaneParams, bool transparent);
Brian Salomonaff27a22017-02-06 15:47:44 -050029 virtual ~SkBaseShadowTessellator() {}
Brian Salomon958fbc42017-01-30 17:01:28 -050030
Brian Salomonaff27a22017-02-06 15:47:44 -050031 sk_sp<SkVertices> releaseVertices() {
32 if (!fSucceeded) {
33 return nullptr;
34 }
Mike Reed887cdf12017-04-03 11:11:09 -040035 return SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode, this->vertexCount(),
Mike Reed97eb4fe2017-03-14 12:04:16 -040036 fPositions.begin(), nullptr, fColors.begin(),
37 this->indexCount(), fIndices.begin());
Brian Salomon1a8b79a2017-01-31 11:26:26 -050038 }
Brian Salomon958fbc42017-01-30 17:01:28 -050039
Jim Van Vertha84898d2017-02-06 13:38:23 -050040protected:
Jim Van Verthda965502017-04-11 15:29:14 -040041 static constexpr auto kMinHeight = 0.1f;
42
Brian Salomonaff27a22017-02-06 15:47:44 -050043 int vertexCount() const { return fPositions.count(); }
44 int indexCount() const { return fIndices.count(); }
45
Jim Van Verthda965502017-04-11 15:29:14 -040046 bool setZOffset(const SkRect& bounds, bool perspective);
47
Jim Van Verthcdaf6612018-06-05 15:21:13 -040048 bool accumulateCentroid(const SkPoint& c, const SkPoint& n);
49 bool checkConvexity(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2);
50 void finishPathPolygon();
Jim Van Verth8760e2f2018-06-12 14:21:38 -040051 void stitchConcaveRings(const SkTDArray<SkPoint>& umbraPolygon,
52 SkTDArray<int>* umbraIndices,
53 const SkTDArray<SkPoint>& penumbraPolygon,
54 SkTDArray<int>* penumbraIndices);
Jim Van Verthcdaf6612018-06-05 15:21:13 -040055
56 void handleLine(const SkPoint& p);
Jim Van Vertha84898d2017-02-06 13:38:23 -050057 void handleLine(const SkMatrix& m, SkPoint* p);
Brian Salomon958fbc42017-01-30 17:01:28 -050058
59 void handleQuad(const SkPoint pts[3]);
Jim Van Vertha84898d2017-02-06 13:38:23 -050060 void handleQuad(const SkMatrix& m, SkPoint pts[3]);
Brian Salomon958fbc42017-01-30 17:01:28 -050061
Jim Van Vertha84898d2017-02-06 13:38:23 -050062 void handleCubic(const SkMatrix& m, SkPoint pts[4]);
Brian Salomon958fbc42017-01-30 17:01:28 -050063
Jim Van Vertha84898d2017-02-06 13:38:23 -050064 void handleConic(const SkMatrix& m, SkPoint pts[3], SkScalar w);
Brian Salomon958fbc42017-01-30 17:01:28 -050065
Jim Van Verthda965502017-04-11 15:29:14 -040066 bool setTransformedHeightFunc(const SkMatrix& ctm);
Brian Salomon958fbc42017-01-30 17:01:28 -050067
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -040068 bool addArc(const SkVector& nextNormal, bool finishArc);
Jim Van Verthb4366552017-03-27 14:25:29 -040069
Jim Van Verth872da6b2018-04-10 11:24:11 -040070 void appendTriangle(uint16_t index0, uint16_t index1, uint16_t index2);
71 void appendQuad(uint16_t index0, uint16_t index1, uint16_t index2, uint16_t index3);
72
Jim Van Verthe308a122017-05-08 14:19:30 -040073 SkScalar heightFunc(SkScalar x, SkScalar y) {
74 return fZPlaneParams.fX*x + fZPlaneParams.fY*y + fZPlaneParams.fZ;
75 }
76
77 SkPoint3 fZPlaneParams;
Jim Van Verthda965502017-04-11 15:29:14 -040078 std::function<SkScalar(const SkPoint&)> fTransformedHeightFunc;
79 SkScalar fZOffset;
80 // members for perspective height function
Jim Van Verth4c9b8932017-05-15 13:49:21 -040081 SkPoint3 fTransformedZParams;
Jim Van Verthda965502017-04-11 15:29:14 -040082 SkScalar fPartialDeterminants[3];
83
Brian Salomon958fbc42017-01-30 17:01:28 -050084 // temporary buffer
85 SkTDArray<SkPoint> fPointBuffer;
Brian Salomon0dda9cb2017-02-03 10:33:25 -050086
Jim Van Vertha84898d2017-02-06 13:38:23 -050087 SkTDArray<SkPoint> fPositions;
88 SkTDArray<SkColor> fColors;
89 SkTDArray<uint16_t> fIndices;
90
Jim Van Verthcdaf6612018-06-05 15:21:13 -040091 SkTDArray<SkPoint> fPathPolygon;
92 SkPoint fCentroid;
93 SkScalar fArea;
94 SkScalar fLastArea;
95 int fAreaSignFlips;
96 SkScalar fLastCross;
97
Jim Van Verth76387852017-05-16 16:55:16 -040098 int fFirstVertexIndex;
99 SkVector fFirstOutset;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500100 SkPoint fFirstPoint;
101
Brian Salomon0dda9cb2017-02-03 10:33:25 -0500102 bool fSucceeded;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500103 bool fTransparent;
Jim Van Verthf507c282018-05-11 10:48:20 -0400104 bool fIsConvex;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500105
106 SkColor fUmbraColor;
107 SkColor fPenumbraColor;
108
109 SkScalar fRadius;
110 SkScalar fDirection;
111 int fPrevUmbraIndex;
Jim Van Verth76387852017-05-16 16:55:16 -0400112 SkVector fPrevOutset;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500113 SkPoint fPrevPoint;
Brian Salomon958fbc42017-01-30 17:01:28 -0500114};
115
Jim Van Verthda965502017-04-11 15:29:14 -0400116static bool compute_normal(const SkPoint& p0, const SkPoint& p1, SkScalar dir,
Jim Van Verthbce74962017-01-25 09:39:46 -0500117 SkVector* newNormal) {
118 SkVector normal;
119 // compute perpendicular
120 normal.fX = p0.fY - p1.fY;
121 normal.fY = p1.fX - p0.fX;
Jim Van Verthda965502017-04-11 15:29:14 -0400122 normal *= dir;
Jim Van Verthbce74962017-01-25 09:39:46 -0500123 if (!normal.normalize()) {
124 return false;
125 }
Jim Van Verthbce74962017-01-25 09:39:46 -0500126 *newNormal = normal;
127 return true;
128}
129
130static void compute_radial_steps(const SkVector& v1, const SkVector& v2, SkScalar r,
131 SkScalar* rotSin, SkScalar* rotCos, int* n) {
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400132 const SkScalar kRecipPixelsPerArcSegment = 0.125f;
Jim Van Verthbce74962017-01-25 09:39:46 -0500133
134 SkScalar rCos = v1.dot(v2);
135 SkScalar rSin = v1.cross(v2);
136 SkScalar theta = SkScalarATan2(rSin, rCos);
137
Jim Van Verth4c8c1e82018-04-23 17:14:48 -0400138 int steps = SkScalarRoundToInt(SkScalarAbs(r*theta*kRecipPixelsPerArcSegment));
Jim Van Verthbce74962017-01-25 09:39:46 -0500139
140 SkScalar dTheta = theta / steps;
141 *rotSin = SkScalarSinCos(dTheta, rotCos);
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400142 *n = steps;
Jim Van Verthbce74962017-01-25 09:39:46 -0500143}
144
Jim Van Verthf507c282018-05-11 10:48:20 -0400145static bool duplicate_pt(const SkPoint& p0, const SkPoint& p1) {
146 static constexpr SkScalar kClose = (SK_Scalar1 / 16);
147 static constexpr SkScalar kCloseSqd = kClose * kClose;
148
149 SkScalar distSq = SkPointPriv::DistanceToSqd(p0, p1);
150 return distSq < kCloseSqd;
151}
152
153static SkScalar perp_dot(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
154 SkVector v0 = p1 - p0;
155 SkVector v1 = p2 - p0;
156 return v0.cross(v1);
157}
158
Jim Van Verthcdaf6612018-06-05 15:21:13 -0400159
Jim Van Verthe308a122017-05-08 14:19:30 -0400160SkBaseShadowTessellator::SkBaseShadowTessellator(const SkPoint3& zPlaneParams, bool transparent)
161 : fZPlaneParams(zPlaneParams)
Jim Van Verthda965502017-04-11 15:29:14 -0400162 , fZOffset(0)
Jim Van Verthcdaf6612018-06-05 15:21:13 -0400163 , fCentroid({0, 0})
164 , fArea(0)
165 , fLastArea(0)
166 , fAreaSignFlips(0)
167 , fLastCross(0)
Jim Van Verth76387852017-05-16 16:55:16 -0400168 , fFirstVertexIndex(-1)
Brian Salomonaff27a22017-02-06 15:47:44 -0500169 , fSucceeded(false)
170 , fTransparent(transparent)
Jim Van Verthf507c282018-05-11 10:48:20 -0400171 , fIsConvex(true)
Brian Salomonaff27a22017-02-06 15:47:44 -0500172 , fDirection(1)
173 , fPrevUmbraIndex(-1) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500174 // child classes will set reserve for positions, colors and indices
175}
176
Jim Van Verthda965502017-04-11 15:29:14 -0400177bool SkBaseShadowTessellator::setZOffset(const SkRect& bounds, bool perspective) {
Jim Van Verthe308a122017-05-08 14:19:30 -0400178 SkScalar minZ = this->heightFunc(bounds.fLeft, bounds.fTop);
Jim Van Verthda965502017-04-11 15:29:14 -0400179 if (perspective) {
Jim Van Verthe308a122017-05-08 14:19:30 -0400180 SkScalar z = this->heightFunc(bounds.fLeft, bounds.fBottom);
Jim Van Verthda965502017-04-11 15:29:14 -0400181 if (z < minZ) {
182 minZ = z;
183 }
Jim Van Verthe308a122017-05-08 14:19:30 -0400184 z = this->heightFunc(bounds.fRight, bounds.fTop);
Jim Van Verthda965502017-04-11 15:29:14 -0400185 if (z < minZ) {
186 minZ = z;
187 }
Jim Van Verthe308a122017-05-08 14:19:30 -0400188 z = this->heightFunc(bounds.fRight, bounds.fBottom);
Jim Van Verthda965502017-04-11 15:29:14 -0400189 if (z < minZ) {
190 minZ = z;
191 }
192 }
193
194 if (minZ < kMinHeight) {
195 fZOffset = -minZ + kMinHeight;
196 return true;
197 }
198
199 return false;
200}
201
Jim Van Verthcdaf6612018-06-05 15:21:13 -0400202bool SkBaseShadowTessellator::accumulateCentroid(const SkPoint& curr, const SkPoint& next) {
203 if (duplicate_pt(curr, next)) {
204 return false;
205 }
206
207 SkScalar quadArea = curr.cross(next);
208 fCentroid.fX += (curr.fX + next.fX) * quadArea;
209 fCentroid.fY += (curr.fY + next.fY) * quadArea;
210 fArea += quadArea;
211 // convexity check
212 if (!SkScalarNearlyZero(quadArea)) {
213 if (quadArea*fLastArea < 0) {
214 ++fAreaSignFlips;
215 }
216 fLastArea = quadArea;
217 }
218
219 return true;
220}
221
222bool SkBaseShadowTessellator::checkConvexity(const SkPoint& p0,
223 const SkPoint& p1,
224 const SkPoint& p2) {
225 SkScalar cross = perp_dot(p0, p1, p2);
226 // skip collinear point
227 if (SkScalarNearlyZero(cross)) {
228 return false;
229 }
230
231 // check for convexity
232 if (fLastCross*cross < 0) {
233 fIsConvex = false;
234 }
235 fLastCross = cross;
236
237 return true;
238}
239
240void SkBaseShadowTessellator::finishPathPolygon() {
241 if (fPathPolygon.count() > 1) {
242 if (!this->accumulateCentroid(fPathPolygon[fPathPolygon.count() - 1], fPathPolygon[0])) {
243 // remove coincident point
244 fPathPolygon.pop();
245 }
246 }
247
248 if (fPathPolygon.count() > 2) {
249 if (!checkConvexity(fPathPolygon[fPathPolygon.count() - 2],
250 fPathPolygon[fPathPolygon.count() - 1],
251 fPathPolygon[0])) {
252 // remove collinear point
253 fPathPolygon[0] = fPathPolygon[fPathPolygon.count() - 1];
254 fPathPolygon.pop();
255 }
256 }
257
258 fCentroid *= sk_ieee_float_divide(1, 3 * fArea);
259 // It's possible to have a concave path that self-intersects but also passes the
260 // cross-product check (e.g., a star). In that case, the signed area will change signs more
261 // than twice, so we check for that here.
262 if (fAreaSignFlips > 2) {
263 fIsConvex = false;
264 }
Jim Van Verth7deacf42018-06-08 15:13:25 -0400265
266 // if area is positive, winding is ccw
267 fDirection = fArea > 0 ? -1 : 1;
Jim Van Verthcdaf6612018-06-05 15:21:13 -0400268}
269
Jim Van Verth8760e2f2018-06-12 14:21:38 -0400270void SkBaseShadowTessellator::stitchConcaveRings(const SkTDArray<SkPoint>& umbraPolygon,
271 SkTDArray<int>* umbraIndices,
272 const SkTDArray<SkPoint>& penumbraPolygon,
273 SkTDArray<int>* penumbraIndices) {
274 // find minimum indices
275 int minIndex = 0;
276 int min = (*penumbraIndices)[0];
277 for (int i = 1; i < (*penumbraIndices).count(); ++i) {
278 if ((*penumbraIndices)[i] < min) {
279 min = (*penumbraIndices)[i];
280 minIndex = i;
281 }
282 }
283 int currPenumbra = minIndex;
284
285 minIndex = 0;
286 min = (*umbraIndices)[0];
287 for (int i = 1; i < (*umbraIndices).count(); ++i) {
288 if ((*umbraIndices)[i] < min) {
289 min = (*umbraIndices)[i];
290 minIndex = i;
291 }
292 }
293 int currUmbra = minIndex;
294
295 // now find a case where the indices are equal (there should be at least one)
296 int maxPenumbraIndex = fPathPolygon.count() - 1;
297 int maxUmbraIndex = fPathPolygon.count() - 1;
298 while ((*penumbraIndices)[currPenumbra] != (*umbraIndices)[currUmbra]) {
299 if ((*penumbraIndices)[currPenumbra] < (*umbraIndices)[currUmbra]) {
300 (*penumbraIndices)[currPenumbra] += fPathPolygon.count();
301 maxPenumbraIndex = (*penumbraIndices)[currPenumbra];
302 currPenumbra = (currPenumbra + 1) % penumbraPolygon.count();
303 } else {
304 (*umbraIndices)[currUmbra] += fPathPolygon.count();
305 maxUmbraIndex = (*umbraIndices)[currUmbra];
306 currUmbra = (currUmbra + 1) % umbraPolygon.count();
307 }
308 }
309
310 *fPositions.push() = penumbraPolygon[currPenumbra];
311 *fColors.push() = fPenumbraColor;
312 int prevPenumbraIndex = 0;
313 *fPositions.push() = umbraPolygon[currUmbra];
314 *fColors.push() = fUmbraColor;
315 fPrevUmbraIndex = 1;
316
317 int nextPenumbra = (currPenumbra + 1) % penumbraPolygon.count();
318 int nextUmbra = (currUmbra + 1) % umbraPolygon.count();
319 while ((*penumbraIndices)[nextPenumbra] <= maxPenumbraIndex ||
320 (*umbraIndices)[nextUmbra] <= maxUmbraIndex) {
321
322 if ((*umbraIndices)[nextUmbra] == (*penumbraIndices)[nextPenumbra]) {
323 // advance both one step
324 *fPositions.push() = penumbraPolygon[nextPenumbra];
325 *fColors.push() = fPenumbraColor;
326 int currPenumbraIndex = fPositions.count() - 1;
327
328 *fPositions.push() = umbraPolygon[nextUmbra];
329 *fColors.push() = fUmbraColor;
330 int currUmbraIndex = fPositions.count() - 1;
331
332 this->appendQuad(prevPenumbraIndex, currPenumbraIndex,
333 fPrevUmbraIndex, currUmbraIndex);
334
335 prevPenumbraIndex = currPenumbraIndex;
336 (*penumbraIndices)[currPenumbra] += fPathPolygon.count();
337 currPenumbra = nextPenumbra;
338 nextPenumbra = (currPenumbra + 1) % penumbraPolygon.count();
339
340 fPrevUmbraIndex = currUmbraIndex;
341 (*umbraIndices)[currUmbra] += fPathPolygon.count();
342 currUmbra = nextUmbra;
343 nextUmbra = (currUmbra + 1) % umbraPolygon.count();
344 }
345
346 while ((*penumbraIndices)[nextPenumbra] < (*umbraIndices)[nextUmbra] &&
347 (*penumbraIndices)[nextPenumbra] <= maxPenumbraIndex) {
348 // fill out penumbra arc
349 *fPositions.push() = penumbraPolygon[nextPenumbra];
350 *fColors.push() = fPenumbraColor;
351 int currPenumbraIndex = fPositions.count() - 1;
352
353 this->appendTriangle(prevPenumbraIndex, currPenumbraIndex, fPrevUmbraIndex);
354
355 prevPenumbraIndex = currPenumbraIndex;
356 // this ensures the ordering when we wrap around
357 (*penumbraIndices)[currPenumbra] += fPathPolygon.count();
358 currPenumbra = nextPenumbra;
359 nextPenumbra = (currPenumbra + 1) % penumbraPolygon.count();
360 }
361
362 while ((*umbraIndices)[nextUmbra] < (*penumbraIndices)[nextPenumbra] &&
363 (*umbraIndices)[nextUmbra] <= maxUmbraIndex) {
364 // fill out umbra arc
365 *fPositions.push() = umbraPolygon[nextUmbra];
366 *fColors.push() = fUmbraColor;
367 int currUmbraIndex = fPositions.count() - 1;
368
369 this->appendTriangle(fPrevUmbraIndex, prevPenumbraIndex, currUmbraIndex);
370
371 fPrevUmbraIndex = currUmbraIndex;
372 // this ensures the ordering when we wrap around
373 (*umbraIndices)[currUmbra] += fPathPolygon.count();
374 currUmbra = nextUmbra;
375 nextUmbra = (currUmbra + 1) % umbraPolygon.count();
376 }
377 }
378 // finish up by advancing both one step
379 *fPositions.push() = penumbraPolygon[nextPenumbra];
380 *fColors.push() = fPenumbraColor;
381 int currPenumbraIndex = fPositions.count() - 1;
382
383 *fPositions.push() = umbraPolygon[nextUmbra];
384 *fColors.push() = fUmbraColor;
385 int currUmbraIndex = fPositions.count() - 1;
386
387 this->appendQuad(prevPenumbraIndex, currPenumbraIndex,
388 fPrevUmbraIndex, currUmbraIndex);
389
390 if (fTransparent) {
391 // TODO: fill penumbra
392 }
393}
394
395
Jim Van Vertha84898d2017-02-06 13:38:23 -0500396// tesselation tolerance values, in device space pixels
Mike Kleinb8b51e62017-02-09 15:22:53 -0500397#if SK_SUPPORT_GPU
Jim Van Vertha84898d2017-02-06 13:38:23 -0500398static const SkScalar kQuadTolerance = 0.2f;
399static const SkScalar kCubicTolerance = 0.2f;
Mike Kleinb8b51e62017-02-09 15:22:53 -0500400#endif
Jim Van Vertha84898d2017-02-06 13:38:23 -0500401static const SkScalar kConicTolerance = 0.5f;
402
Jim Van Verthcdaf6612018-06-05 15:21:13 -0400403void SkBaseShadowTessellator::handleLine(const SkPoint& p) {
404 if (fPathPolygon.count() > 0) {
405 if (!this->accumulateCentroid(fPathPolygon[fPathPolygon.count() - 1], p)) {
406 // skip coincident point
407 return;
408 }
409 }
410
411 if (fPathPolygon.count() > 1) {
412 if (!checkConvexity(fPathPolygon[fPathPolygon.count() - 2],
413 fPathPolygon[fPathPolygon.count() - 1],
414 p)) {
415 // remove collinear point
416 fPathPolygon.pop();
417 }
418 }
419
420 *fPathPolygon.push() = p;
421}
422
Brian Salomonaff27a22017-02-06 15:47:44 -0500423void SkBaseShadowTessellator::handleLine(const SkMatrix& m, SkPoint* p) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500424 m.mapPoints(p, 1);
Jim Van Verthcdaf6612018-06-05 15:21:13 -0400425
Jim Van Vertha84898d2017-02-06 13:38:23 -0500426 this->handleLine(*p);
427}
428
Brian Salomonaff27a22017-02-06 15:47:44 -0500429void SkBaseShadowTessellator::handleQuad(const SkPoint pts[3]) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500430#if SK_SUPPORT_GPU
Jim Van Vertha947e292018-02-26 13:54:34 -0500431 // check for degeneracy
432 SkVector v0 = pts[1] - pts[0];
433 SkVector v1 = pts[2] - pts[0];
434 if (SkScalarNearlyZero(v0.cross(v1))) {
435 return;
436 }
Jim Van Vertha84898d2017-02-06 13:38:23 -0500437 // TODO: Pull PathUtils out of Ganesh?
438 int maxCount = GrPathUtils::quadraticPointCount(pts, kQuadTolerance);
Mike Kleincc9856c2018-04-19 09:18:33 -0400439 fPointBuffer.setCount(maxCount);
Jim Van Vertha84898d2017-02-06 13:38:23 -0500440 SkPoint* target = fPointBuffer.begin();
441 int count = GrPathUtils::generateQuadraticPoints(pts[0], pts[1], pts[2],
442 kQuadTolerance, &target, maxCount);
443 fPointBuffer.setCount(count);
444 for (int i = 0; i < count; i++) {
445 this->handleLine(fPointBuffer[i]);
446 }
447#else
448 // for now, just to draw something
449 this->handleLine(pts[1]);
450 this->handleLine(pts[2]);
451#endif
452}
453
Brian Salomonaff27a22017-02-06 15:47:44 -0500454void SkBaseShadowTessellator::handleQuad(const SkMatrix& m, SkPoint pts[3]) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500455 m.mapPoints(pts, 3);
456 this->handleQuad(pts);
457}
458
Brian Salomonaff27a22017-02-06 15:47:44 -0500459void SkBaseShadowTessellator::handleCubic(const SkMatrix& m, SkPoint pts[4]) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500460 m.mapPoints(pts, 4);
461#if SK_SUPPORT_GPU
462 // TODO: Pull PathUtils out of Ganesh?
463 int maxCount = GrPathUtils::cubicPointCount(pts, kCubicTolerance);
Mike Kleincc9856c2018-04-19 09:18:33 -0400464 fPointBuffer.setCount(maxCount);
Jim Van Vertha84898d2017-02-06 13:38:23 -0500465 SkPoint* target = fPointBuffer.begin();
466 int count = GrPathUtils::generateCubicPoints(pts[0], pts[1], pts[2], pts[3],
467 kCubicTolerance, &target, maxCount);
468 fPointBuffer.setCount(count);
469 for (int i = 0; i < count; i++) {
470 this->handleLine(fPointBuffer[i]);
471 }
472#else
473 // for now, just to draw something
474 this->handleLine(pts[1]);
475 this->handleLine(pts[2]);
476 this->handleLine(pts[3]);
477#endif
478}
479
Brian Salomonaff27a22017-02-06 15:47:44 -0500480void SkBaseShadowTessellator::handleConic(const SkMatrix& m, SkPoint pts[3], SkScalar w) {
Jim Van Verthda965502017-04-11 15:29:14 -0400481 if (m.hasPerspective()) {
482 w = SkConic::TransformW(pts, w, m);
483 }
Jim Van Vertha84898d2017-02-06 13:38:23 -0500484 m.mapPoints(pts, 3);
485 SkAutoConicToQuads quadder;
486 const SkPoint* quads = quadder.computeQuads(pts, w, kConicTolerance);
487 SkPoint lastPoint = *(quads++);
488 int count = quadder.countQuads();
489 for (int i = 0; i < count; ++i) {
490 SkPoint quadPts[3];
491 quadPts[0] = lastPoint;
492 quadPts[1] = quads[0];
493 quadPts[2] = i == count - 1 ? pts[2] : quads[1];
494 this->handleQuad(quadPts);
495 lastPoint = quadPts[2];
496 quads += 2;
497 }
498}
499
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400500bool SkBaseShadowTessellator::addArc(const SkVector& nextNormal, bool finishArc) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500501 // fill in fan from previous quad
502 SkScalar rotSin, rotCos;
503 int numSteps;
Jim Van Verth76387852017-05-16 16:55:16 -0400504 compute_radial_steps(fPrevOutset, nextNormal, fRadius, &rotSin, &rotCos, &numSteps);
505 SkVector prevNormal = fPrevOutset;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400506 for (int i = 0; i < numSteps-1; ++i) {
Jim Van Verthda965502017-04-11 15:29:14 -0400507 SkVector currNormal;
508 currNormal.fX = prevNormal.fX*rotCos - prevNormal.fY*rotSin;
509 currNormal.fY = prevNormal.fY*rotCos + prevNormal.fX*rotSin;
510 *fPositions.push() = fPrevPoint + currNormal;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500511 *fColors.push() = fPenumbraColor;
Jim Van Verth872da6b2018-04-10 11:24:11 -0400512 this->appendTriangle(fPrevUmbraIndex, fPositions.count() - 1, fPositions.count() - 2);
Jim Van Vertha84898d2017-02-06 13:38:23 -0500513
Jim Van Verthda965502017-04-11 15:29:14 -0400514 prevNormal = currNormal;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500515 }
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400516 if (finishArc && numSteps) {
Jim Van Verthda965502017-04-11 15:29:14 -0400517 *fPositions.push() = fPrevPoint + nextNormal;
518 *fColors.push() = fPenumbraColor;
Jim Van Verth872da6b2018-04-10 11:24:11 -0400519 this->appendTriangle(fPrevUmbraIndex, fPositions.count() - 1, fPositions.count() - 2);
Jim Van Verthda965502017-04-11 15:29:14 -0400520 }
Jim Van Verth76387852017-05-16 16:55:16 -0400521 fPrevOutset = nextNormal;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400522
523 return (numSteps > 0);
Jim Van Vertha84898d2017-02-06 13:38:23 -0500524}
525
Jim Van Verth872da6b2018-04-10 11:24:11 -0400526void SkBaseShadowTessellator::appendTriangle(uint16_t index0, uint16_t index1, uint16_t index2) {
527 auto indices = fIndices.append(3);
528
529 indices[0] = index0;
530 indices[1] = index1;
531 indices[2] = index2;
532}
533
534void SkBaseShadowTessellator::appendQuad(uint16_t index0, uint16_t index1,
535 uint16_t index2, uint16_t index3) {
536 auto indices = fIndices.append(6);
537
538 indices[0] = index0;
539 indices[1] = index1;
540 indices[2] = index2;
541
542 indices[3] = index2;
543 indices[4] = index1;
544 indices[5] = index3;
545}
546
Jim Van Verthda965502017-04-11 15:29:14 -0400547bool SkBaseShadowTessellator::setTransformedHeightFunc(const SkMatrix& ctm) {
Jim Van Verth4c9b8932017-05-15 13:49:21 -0400548 if (SkScalarNearlyZero(fZPlaneParams.fX) && SkScalarNearlyZero(fZPlaneParams.fY)) {
Jim Van Verthda965502017-04-11 15:29:14 -0400549 fTransformedHeightFunc = [this](const SkPoint& p) {
Jim Van Verthe308a122017-05-08 14:19:30 -0400550 return fZPlaneParams.fZ;
Jim Van Verthda965502017-04-11 15:29:14 -0400551 };
552 } else {
553 SkMatrix ctmInverse;
Jim Van Vertha947e292018-02-26 13:54:34 -0500554 if (!ctm.invert(&ctmInverse) || !ctmInverse.isFinite()) {
Jim Van Verthda965502017-04-11 15:29:14 -0400555 return false;
556 }
Jim Van Verthda965502017-04-11 15:29:14 -0400557 // multiply by transpose
Jim Van Verth4c9b8932017-05-15 13:49:21 -0400558 fTransformedZParams = SkPoint3::Make(
Jim Van Verthe308a122017-05-08 14:19:30 -0400559 ctmInverse[SkMatrix::kMScaleX] * fZPlaneParams.fX +
560 ctmInverse[SkMatrix::kMSkewY] * fZPlaneParams.fY +
561 ctmInverse[SkMatrix::kMPersp0] * fZPlaneParams.fZ,
562
563 ctmInverse[SkMatrix::kMSkewX] * fZPlaneParams.fX +
564 ctmInverse[SkMatrix::kMScaleY] * fZPlaneParams.fY +
565 ctmInverse[SkMatrix::kMPersp1] * fZPlaneParams.fZ,
566
567 ctmInverse[SkMatrix::kMTransX] * fZPlaneParams.fX +
568 ctmInverse[SkMatrix::kMTransY] * fZPlaneParams.fY +
569 ctmInverse[SkMatrix::kMPersp2] * fZPlaneParams.fZ
570 );
Jim Van Verthda965502017-04-11 15:29:14 -0400571
Jim Van Verth4c9b8932017-05-15 13:49:21 -0400572 if (ctm.hasPerspective()) {
573 // We use Cramer's rule to solve for the W value for a given post-divide X and Y,
574 // so pre-compute those values that are independent of X and Y.
575 // W is det(ctmInverse)/(PD[0]*X + PD[1]*Y + PD[2])
576 fPartialDeterminants[0] = ctm[SkMatrix::kMSkewY] * ctm[SkMatrix::kMPersp1] -
577 ctm[SkMatrix::kMScaleY] * ctm[SkMatrix::kMPersp0];
578 fPartialDeterminants[1] = ctm[SkMatrix::kMPersp0] * ctm[SkMatrix::kMSkewX] -
579 ctm[SkMatrix::kMPersp1] * ctm[SkMatrix::kMScaleX];
580 fPartialDeterminants[2] = ctm[SkMatrix::kMScaleX] * ctm[SkMatrix::kMScaleY] -
581 ctm[SkMatrix::kMSkewX] * ctm[SkMatrix::kMSkewY];
582 SkScalar ctmDeterminant = ctm[SkMatrix::kMTransX] * fPartialDeterminants[0] +
583 ctm[SkMatrix::kMTransY] * fPartialDeterminants[1] +
584 ctm[SkMatrix::kMPersp2] * fPartialDeterminants[2];
Jim Van Verthda965502017-04-11 15:29:14 -0400585
Jim Van Verth4c9b8932017-05-15 13:49:21 -0400586 // Pre-bake the numerator of Cramer's rule into the zParams to avoid another multiply.
587 // TODO: this may introduce numerical instability, but I haven't seen any issues yet.
588 fTransformedZParams.fX *= ctmDeterminant;
589 fTransformedZParams.fY *= ctmDeterminant;
590 fTransformedZParams.fZ *= ctmDeterminant;
Jim Van Verthda965502017-04-11 15:29:14 -0400591
Jim Van Verth4c9b8932017-05-15 13:49:21 -0400592 fTransformedHeightFunc = [this](const SkPoint& p) {
593 SkScalar denom = p.fX * fPartialDeterminants[0] +
594 p.fY * fPartialDeterminants[1] +
595 fPartialDeterminants[2];
596 SkScalar w = SkScalarFastInvert(denom);
597 return fZOffset + w*(fTransformedZParams.fX * p.fX +
598 fTransformedZParams.fY * p.fY +
599 fTransformedZParams.fZ);
600 };
601 } else {
602 fTransformedHeightFunc = [this](const SkPoint& p) {
603 return fZOffset + fTransformedZParams.fX * p.fX +
604 fTransformedZParams.fY * p.fY + fTransformedZParams.fZ;
605 };
606 }
Jim Van Verthda965502017-04-11 15:29:14 -0400607 }
608
609 return true;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500610}
611
612
613//////////////////////////////////////////////////////////////////////////////////////////////////
614
Brian Salomonaff27a22017-02-06 15:47:44 -0500615class SkAmbientShadowTessellator : public SkBaseShadowTessellator {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500616public:
617 SkAmbientShadowTessellator(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -0400618 const SkPoint3& zPlaneParams, bool transparent);
Jim Van Vertha84898d2017-02-06 13:38:23 -0500619
620private:
Jim Van Verthcdaf6612018-06-05 15:21:13 -0400621 void computePathPolygon(const SkPath& path, const SkMatrix& ctm);
Jim Van Verth8760e2f2018-06-12 14:21:38 -0400622 bool computeConvexShadow();
623 bool computeConcaveShadow();
624
Jim Van Verth7deacf42018-06-08 15:13:25 -0400625 void handlePolyPoint(const SkPoint& p, bool finalPoint);
626 void addEdge(const SkPoint& nextPoint, const SkVector& nextNormal, bool finalEdge);
627 void splitEdge(const SkPoint& nextPoint, const SkVector& insetNormal,
628 const SkPoint& penumbraPoint, const SkPoint& umbraPoint, SkColor umbraColor);
Jim Van Vertha84898d2017-02-06 13:38:23 -0500629
Jim Van Verthda965502017-04-11 15:29:14 -0400630 static constexpr auto kMaxEdgeLenSqr = 20 * 20;
Jim Van Verth76387852017-05-16 16:55:16 -0400631 static constexpr auto kInsetFactor = -0.5f;
Jim Van Verthda965502017-04-11 15:29:14 -0400632
633 SkScalar offset(SkScalar z) {
Jim Van Verth1af03d42017-07-31 09:34:58 -0400634 return SkDrawShadowMetrics::AmbientBlurRadius(z);
Jim Van Verthda965502017-04-11 15:29:14 -0400635 }
636 SkColor umbraColor(SkScalar z) {
Jim Van Verth1af03d42017-07-31 09:34:58 -0400637 SkScalar umbraAlpha = SkScalarInvert(SkDrawShadowMetrics::AmbientRecipAlpha(z));
Jim Van Verth060d9822017-05-04 09:58:17 -0400638 return SkColorSetARGB(umbraAlpha * 255.9999f, 0, 0, 0);
Jim Van Verthda965502017-04-11 15:29:14 -0400639 }
640
Jim Van Verth76387852017-05-16 16:55:16 -0400641 bool fSplitFirstEdge;
642 bool fSplitPreviousEdge;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500643
Brian Salomonaff27a22017-02-06 15:47:44 -0500644 typedef SkBaseShadowTessellator INHERITED;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500645};
646
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500647SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path,
Jim Van Vertha84898d2017-02-06 13:38:23 -0500648 const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -0400649 const SkPoint3& zPlaneParams,
Jim Van Verthbce74962017-01-25 09:39:46 -0500650 bool transparent)
Jim Van Verth76387852017-05-16 16:55:16 -0400651 : INHERITED(zPlaneParams, transparent)
652 , fSplitFirstEdge(false)
653 , fSplitPreviousEdge(false) {
Jim Van Verthda965502017-04-11 15:29:14 -0400654 // Set base colors
Jim Van Verth1af03d42017-07-31 09:34:58 -0400655 SkScalar umbraAlpha = SkScalarInvert(SkDrawShadowMetrics::AmbientRecipAlpha(heightFunc(0, 0)));
Jim Van Verthb4366552017-03-27 14:25:29 -0400656 // umbraColor is the interior value, penumbraColor the exterior value.
657 // umbraAlpha is the factor that is linearly interpolated from outside to inside, and
658 // then "blurred" by the GrBlurredEdgeFP. It is then multiplied by fAmbientAlpha to get
659 // the final alpha.
Jim Van Verth060d9822017-05-04 09:58:17 -0400660 fUmbraColor = SkColorSetARGB(umbraAlpha * 255.9999f, 0, 0, 0);
661 fPenumbraColor = SkColorSetARGB(0, 0, 0, 0);
Jim Van Verthb4366552017-03-27 14:25:29 -0400662
Jim Van Verthda965502017-04-11 15:29:14 -0400663 // make sure we're not below the canvas plane
664 this->setZOffset(path.getBounds(), ctm.hasPerspective());
665
Jim Van Verth7c8008c2018-02-07 15:02:50 -0500666 if (!this->setTransformedHeightFunc(ctm)) {
667 return;
668 }
Jim Van Verthda965502017-04-11 15:29:14 -0400669
Jim Van Verthbce74962017-01-25 09:39:46 -0500670 // Outer ring: 3*numPts
671 // Middle ring: numPts
672 fPositions.setReserve(4 * path.countPoints());
673 fColors.setReserve(4 * path.countPoints());
674 // Outer ring: 12*numPts
675 // Middle ring: 0
676 fIndices.setReserve(12 * path.countPoints());
677
Jim Van Verthcdaf6612018-06-05 15:21:13 -0400678 this->computePathPolygon(path, ctm);
Jim Van Verth8760e2f2018-06-12 14:21:38 -0400679 if (fIsConvex) {
680 fSucceeded = this->computeConvexShadow();
681 } else {
682 fSucceeded = this->computeConcaveShadow();
683 }
684}
685
686void SkAmbientShadowTessellator::computePathPolygon(const SkPath& path, const SkMatrix& ctm) {
687 fPathPolygon.setReserve(path.countPoints());
688
689 // walk around the path, tessellate and generate outer ring
690 // if original path is transparent, will accumulate sum of points for centroid
691 SkPath::Iter iter(path, true);
692 SkPoint pts[4];
693 SkPath::Verb verb;
694 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
695 switch (verb) {
696 case SkPath::kLine_Verb:
697 this->handleLine(ctm, &pts[1]);
698 break;
699 case SkPath::kQuad_Verb:
700 this->handleQuad(ctm, pts);
701 break;
702 case SkPath::kCubic_Verb:
703 this->handleCubic(ctm, pts);
704 break;
705 case SkPath::kConic_Verb:
706 this->handleConic(ctm, pts, iter.conicWeight());
707 break;
708 case SkPath::kMove_Verb:
709 case SkPath::kClose_Verb:
710 case SkPath::kDone_Verb:
711 break;
712 }
713 }
714
715 this->finishPathPolygon();
716}
717
718bool SkAmbientShadowTessellator::computeConvexShadow() {
Jim Van Verth7deacf42018-06-08 15:13:25 -0400719 int polyCount = fPathPolygon.count();
720 if (polyCount < 3) {
Jim Van Verth8760e2f2018-06-12 14:21:38 -0400721 return false;
Brian Salomon0dda9cb2017-02-03 10:33:25 -0500722 }
723
Jim Van Verthcdaf6612018-06-05 15:21:13 -0400724 // Add center point for fan if needed
725 if (fTransparent) {
726 *fPositions.push() = fCentroid;
727 *fColors.push() = this->umbraColor(fTransformedHeightFunc(fCentroid));
728 }
729
Jim Van Verth7deacf42018-06-08 15:13:25 -0400730 // Initialize
731 SkVector normal;
732 if (!compute_normal(fPathPolygon[polyCount-1], fPathPolygon[0], fDirection, &normal)) {
733 // the polygon should be sanitized, so any issues at this point are unrecoverable
Jim Van Verth8760e2f2018-06-12 14:21:38 -0400734 return false;
Jim Van Verthf507c282018-05-11 10:48:20 -0400735 }
Jim Van Verth7deacf42018-06-08 15:13:25 -0400736 fFirstPoint = fPathPolygon[polyCount - 1];
737 fFirstVertexIndex = fPositions.count();
738 SkScalar z = fTransformedHeightFunc(fFirstPoint);
739 fFirstOutset = normal;
740 fFirstOutset *= this->offset(z);
Jim Van Verthf507c282018-05-11 10:48:20 -0400741
Jim Van Verth7deacf42018-06-08 15:13:25 -0400742 fPrevOutset = fFirstOutset;
743 fPrevPoint = fFirstPoint;
744 fPrevUmbraIndex = fFirstVertexIndex;
Jim Van Verthbce74962017-01-25 09:39:46 -0500745
Jim Van Verth7deacf42018-06-08 15:13:25 -0400746 // Add the first quad
747 *fPositions.push() = fFirstPoint;
748 *fColors.push() = this->umbraColor(z);
749 *fPositions.push() = fFirstPoint + fFirstOutset;
750 *fColors.push() = fPenumbraColor;
Jim Van Verth76387852017-05-16 16:55:16 -0400751
Jim Van Verth7deacf42018-06-08 15:13:25 -0400752 z = fTransformedHeightFunc(fPathPolygon[0]);
753 fRadius = this->offset(z);
754 fUmbraColor = this->umbraColor(z);
755 this->addEdge(fPathPolygon[0], normal, false);
Jim Van Verthbce74962017-01-25 09:39:46 -0500756
Jim Van Verth7deacf42018-06-08 15:13:25 -0400757 // Process the remaining points
758 for (int i = 1; i < fPathPolygon.count(); ++i) {
759 this->handlePolyPoint(fPathPolygon[i], i == fPathPolygon.count()-1);
760 }
761 SkASSERT(this->indexCount());
Jim Van Verthda965502017-04-11 15:29:14 -0400762
Jim Van Verth7deacf42018-06-08 15:13:25 -0400763 // Final fan
764 SkASSERT(fPositions.count() >= 3);
765 if (this->addArc(fFirstOutset, false)) {
766 this->appendTriangle(fFirstVertexIndex, fPositions.count() - 1, fFirstVertexIndex + 1);
767 } else {
768 // arc is too small, set the first penumbra point to be the same position
769 // as the last one
770 fPositions[fFirstVertexIndex + 1] = fPositions[fPositions.count() - 1];
Jim Van Verthbce74962017-01-25 09:39:46 -0500771 }
772
Jim Van Verth8760e2f2018-06-12 14:21:38 -0400773 return true;
Jim Van Verthbce74962017-01-25 09:39:46 -0500774}
775
Jim Van Verth8760e2f2018-06-12 14:21:38 -0400776bool SkAmbientShadowTessellator::computeConcaveShadow() {
777 // TODO: remove when we support filling the penumbra
778 if (fTransparent) {
779 return false;
Jim Van Verthf507c282018-05-11 10:48:20 -0400780 }
781
Jim Van Verth8760e2f2018-06-12 14:21:38 -0400782 // generate inner ring
783 SkTDArray<SkPoint> umbraPolygon;
784 SkTDArray<int> umbraIndices;
785 umbraIndices.setReserve(fPathPolygon.count());
786 if (!SkOffsetSimplePolygon(&fPathPolygon[0], fPathPolygon.count(), 0.5f,
787 &umbraPolygon, &umbraIndices)) {
788 // TODO: figure out how to handle this case
789 return false;
790 }
791
792 // generate outer ring
793 SkTDArray<SkPoint> penumbraPolygon;
794 SkTDArray<int> penumbraIndices;
795 penumbraPolygon.setReserve(umbraPolygon.count());
796 penumbraIndices.setReserve(umbraPolygon.count());
Jim Van Verthe39bc9f2018-06-14 14:43:00 -0400797
798 auto offsetFunc = [this](const SkPoint& p) { return -this->offset(fTransformedHeightFunc(p)); };
799 if (!SkOffsetSimplePolygon(&fPathPolygon[0], fPathPolygon.count(), offsetFunc,
Jim Van Verth8760e2f2018-06-12 14:21:38 -0400800 &penumbraPolygon, &penumbraIndices)) {
801 // TODO: figure out how to handle this case
802 return false;
803 }
804
805 if (!umbraPolygon.count() || !penumbraPolygon.count()) {
806 return false;
807 }
808
809 // attach the rings together
810 this->stitchConcaveRings(umbraPolygon, &umbraIndices, penumbraPolygon, &penumbraIndices);
811
812 return true;
Jim Van Verthcdaf6612018-06-05 15:21:13 -0400813}
814
Jim Van Verth7deacf42018-06-08 15:13:25 -0400815void SkAmbientShadowTessellator::handlePolyPoint(const SkPoint& p, bool finalPoint) {
Jim Van Verthbce74962017-01-25 09:39:46 -0500816 SkVector normal;
Jim Van Verth76387852017-05-16 16:55:16 -0400817 if (compute_normal(fPrevPoint, p, fDirection, &normal)) {
Jim Van Verthda965502017-04-11 15:29:14 -0400818 SkVector scaledNormal = normal;
819 scaledNormal *= fRadius;
820 this->addArc(scaledNormal, true);
821 SkScalar z = fTransformedHeightFunc(p);
822 fRadius = this->offset(z);
823 fUmbraColor = this->umbraColor(z);
Jim Van Verth7deacf42018-06-08 15:13:25 -0400824 this->addEdge(p, normal, finalPoint);
Jim Van Verthbce74962017-01-25 09:39:46 -0500825 }
826}
827
Jim Van Verth7deacf42018-06-08 15:13:25 -0400828void SkAmbientShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVector& nextNormal,
829 bool finalEdge) {
Jim Van Verth76387852017-05-16 16:55:16 -0400830 // We compute the inset in two stages: first we inset by half the current normal,
831 // then on the next addEdge() we add half of the next normal to get an average of the two
832 SkVector insetNormal = nextNormal;
833 insetNormal *= 0.5f*kInsetFactor;
834
835 // Adding the other half of the average for the previous edge
836 fPositions[fPrevUmbraIndex] += insetNormal;
837
Jim Van Verth7deacf42018-06-08 15:13:25 -0400838 SkPoint umbraPoint;
839 if (finalEdge) {
840 // Again, adding the other half of the average for the previous edge
841 fPositions[fFirstVertexIndex] += insetNormal;
842 // we multiply by another half because now we're adding to an average of an average
843 if (fSplitFirstEdge) {
844 fPositions[fFirstVertexIndex + 2] += insetNormal * 0.5f;
845 }
846 umbraPoint = fPositions[fFirstVertexIndex];
847 } else {
848 umbraPoint = nextPoint + insetNormal;
849 }
Jim Van Verth76387852017-05-16 16:55:16 -0400850 SkVector outsetNormal = nextNormal;
851 outsetNormal *= fRadius;
852 SkPoint penumbraPoint = nextPoint + outsetNormal;
853
Jim Van Verth7deacf42018-06-08 15:13:25 -0400854 // make sure we don't end up with a sharp alpha edge along the quad diagonal
855 this->splitEdge(nextPoint, insetNormal, penumbraPoint, umbraPoint, fUmbraColor);
856
857 // add next quad
858 int prevPenumbraIndex;
859 int currUmbraIndex;
860 if (finalEdge) {
861 prevPenumbraIndex = fPositions.count() - 1;
862 currUmbraIndex = fFirstVertexIndex;
863 } else {
864 prevPenumbraIndex = fPositions.count() - 1;
865 *fPositions.push() = umbraPoint;
866 *fColors.push() = fUmbraColor;
867 currUmbraIndex = fPositions.count() - 1;
868 }
869
870 *fPositions.push() = penumbraPoint;
871 *fColors.push() = fPenumbraColor;
872
873 // set triangularization to get best interpolation of color
874 if (fColors[fPrevUmbraIndex] > fUmbraColor) {
875 this->appendQuad(fPrevUmbraIndex, prevPenumbraIndex,
876 currUmbraIndex, fPositions.count() - 1);
877 } else {
878 this->appendQuad(currUmbraIndex, fPositions.count() - 1,
879 fPrevUmbraIndex, prevPenumbraIndex);
880 }
881
882 // if transparent, add to center fan
883 if (fTransparent) {
884 this->appendTriangle(0, fPrevUmbraIndex, currUmbraIndex);
885 }
886
887 fPrevUmbraIndex = currUmbraIndex;
888 fPrevPoint = nextPoint;
889 fPrevOutset = outsetNormal;
890}
891
892void SkAmbientShadowTessellator::splitEdge(const SkPoint& nextPoint, const SkVector& insetNormal,
893 const SkPoint& penumbraPoint, const SkPoint& umbraPoint,
894 SkColor umbraColor) {
Jim Van Verth76387852017-05-16 16:55:16 -0400895 // For split edges, we're adding an average of two averages, so we multiply by another half
896 if (fSplitPreviousEdge) {
Jim Van Verth7deacf42018-06-08 15:13:25 -0400897 fPositions[fPrevUmbraIndex - 2] += insetNormal*SK_ScalarHalf;
Jim Van Verth76387852017-05-16 16:55:16 -0400898 }
899
900 // Split the edge to make sure we don't end up with a sharp alpha edge along the quad diagonal
Jim Van Verth7deacf42018-06-08 15:13:25 -0400901 if (fColors[fPrevUmbraIndex] != umbraColor &&
Cary Clarkdf429f32017-11-08 11:44:31 -0500902 SkPointPriv::DistanceToSqd(nextPoint, fPositions[fPrevUmbraIndex]) > kMaxEdgeLenSqr) {
Jim Van Verth76387852017-05-16 16:55:16 -0400903
904 // This is lacking 1/4 of the next inset -- we'll add it the next time we call addEdge()
905 SkPoint centerPoint = fPositions[fPrevUmbraIndex] + umbraPoint;
Jim Van Verthda965502017-04-11 15:29:14 -0400906 centerPoint *= 0.5f;
907 *fPositions.push() = centerPoint;
Jim Van Verth7deacf42018-06-08 15:13:25 -0400908 *fColors.push() = SkPMLerp(umbraColor, fColors[fPrevUmbraIndex], 128);
909 centerPoint = fPositions[fPositions.count() - 2] + penumbraPoint;
Jim Van Verth76387852017-05-16 16:55:16 -0400910 centerPoint *= 0.5f;
911 *fPositions.push() = centerPoint;
Jim Van Verthda965502017-04-11 15:29:14 -0400912 *fColors.push() = fPenumbraColor;
913
914 // set triangularization to get best interpolation of color
915 if (fColors[fPrevUmbraIndex] > fColors[fPositions.count() - 2]) {
Jim Van Verth872da6b2018-04-10 11:24:11 -0400916 this->appendQuad(fPrevUmbraIndex, fPositions.count() - 3,
917 fPositions.count() - 2, fPositions.count() - 1);
Jim Van Verthda965502017-04-11 15:29:14 -0400918 } else {
Jim Van Verth872da6b2018-04-10 11:24:11 -0400919 this->appendQuad(fPositions.count() - 2, fPositions.count() - 1,
920 fPrevUmbraIndex, fPositions.count() - 3);
Jim Van Verthda965502017-04-11 15:29:14 -0400921 }
922
Jim Van Verthcdaf6612018-06-05 15:21:13 -0400923 // if transparent, add to center fan
Jim Van Verth1c4c1142017-05-11 10:23:53 -0400924 if (fTransparent) {
Jim Van Verth872da6b2018-04-10 11:24:11 -0400925 this->appendTriangle(0, fPrevUmbraIndex, fPositions.count() - 2);
Jim Van Verth1c4c1142017-05-11 10:23:53 -0400926 }
927
Jim Van Verth76387852017-05-16 16:55:16 -0400928 fSplitPreviousEdge = true;
929 if (fPrevUmbraIndex == fFirstVertexIndex) {
930 fSplitFirstEdge = true;
931 }
Jim Van Verthda965502017-04-11 15:29:14 -0400932 fPrevUmbraIndex = fPositions.count() - 2;
Jim Van Verth76387852017-05-16 16:55:16 -0400933 } else {
934 fSplitPreviousEdge = false;
Jim Van Verthda965502017-04-11 15:29:14 -0400935 }
Jim Van Verthbce74962017-01-25 09:39:46 -0500936}
Jim Van Verth91af7272017-01-27 14:15:54 -0500937
Jim Van Verth7deacf42018-06-08 15:13:25 -0400938
Jim Van Verth91af7272017-01-27 14:15:54 -0500939///////////////////////////////////////////////////////////////////////////////////////////////////
940
Brian Salomonaff27a22017-02-06 15:47:44 -0500941class SkSpotShadowTessellator : public SkBaseShadowTessellator {
Brian Salomon958fbc42017-01-30 17:01:28 -0500942public:
Jim Van Vertha84898d2017-02-06 13:38:23 -0500943 SkSpotShadowTessellator(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -0400944 const SkPoint3& zPlaneParams, const SkPoint3& lightPos,
Jim Van Verth060d9822017-05-04 09:58:17 -0400945 SkScalar lightRadius, bool transparent);
Brian Salomon958fbc42017-01-30 17:01:28 -0500946
Brian Salomon958fbc42017-01-30 17:01:28 -0500947private:
Brian Salomonab664fa2017-03-24 16:07:20 +0000948 void computeClipAndPathPolygons(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthda965502017-04-11 15:29:14 -0400949 const SkMatrix& shadowTransform);
Brian Salomonab664fa2017-03-24 16:07:20 +0000950 void computeClipVectorsAndTestCentroid();
Brian Salomon66085ed2017-02-02 15:39:34 -0500951 bool clipUmbraPoint(const SkPoint& umbraPoint, const SkPoint& centroid, SkPoint* clipPoint);
Brian Salomonab664fa2017-03-24 16:07:20 +0000952 int getClosestUmbraPoint(const SkPoint& point);
Brian Salomon958fbc42017-01-30 17:01:28 -0500953
Jim Van Verth872da6b2018-04-10 11:24:11 -0400954 bool computeConvexShadow(SkScalar radius);
955 bool computeConcaveShadow(SkScalar radius);
956
Jim Van Verth7deacf42018-06-08 15:13:25 -0400957 bool handlePolyPoint(const SkPoint& p, bool lastPoint);
Brian Salomon958fbc42017-01-30 17:01:28 -0500958
959 void mapPoints(SkScalar scale, const SkVector& xlate, SkPoint* pts, int count);
Jim Van Verth8c2de8f2018-05-14 11:20:58 -0400960 bool addInnerPoint(const SkPoint& pathPoint, int* currUmbraIndex);
Jim Van Verth7deacf42018-06-08 15:13:25 -0400961 void addEdge(const SkVector& nextPoint, const SkVector& nextNormal, bool lastEdge);
Jim Van Verthf507c282018-05-11 10:48:20 -0400962 void addToClip(const SkVector& nextPoint);
Jim Van Verthda965502017-04-11 15:29:14 -0400963
964 SkScalar offset(SkScalar z) {
965 float zRatio = SkTPin(z / (fLightZ - z), 0.0f, 0.95f);
966 return fLightRadius*zRatio;
967 }
968
969 SkScalar fLightZ;
970 SkScalar fLightRadius;
971 SkScalar fOffsetAdjust;
Brian Salomon958fbc42017-01-30 17:01:28 -0500972
Brian Salomon958fbc42017-01-30 17:01:28 -0500973 SkTDArray<SkPoint> fClipPolygon;
Brian Salomon66085ed2017-02-02 15:39:34 -0500974 SkTDArray<SkVector> fClipVectors;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500975
Brian Salomonab664fa2017-03-24 16:07:20 +0000976 SkTDArray<SkPoint> fUmbraPolygon;
977 int fCurrClipPoint;
978 int fCurrUmbraPoint;
Brian Salomon66085ed2017-02-02 15:39:34 -0500979 bool fPrevUmbraOutside;
980 bool fFirstUmbraOutside;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500981 bool fValidUmbra;
Brian Salomon958fbc42017-01-30 17:01:28 -0500982
Brian Salomonaff27a22017-02-06 15:47:44 -0500983 typedef SkBaseShadowTessellator INHERITED;
Brian Salomon958fbc42017-01-30 17:01:28 -0500984};
985
Jim Van Vertha84898d2017-02-06 13:38:23 -0500986SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -0400987 const SkPoint3& zPlaneParams,
Jim Van Verthb4366552017-03-27 14:25:29 -0400988 const SkPoint3& lightPos, SkScalar lightRadius,
Jim Van Verth060d9822017-05-04 09:58:17 -0400989 bool transparent)
Jim Van Verthe308a122017-05-08 14:19:30 -0400990 : INHERITED(zPlaneParams, transparent)
Jim Van Verthda965502017-04-11 15:29:14 -0400991 , fLightZ(lightPos.fZ)
992 , fLightRadius(lightRadius)
993 , fOffsetAdjust(0)
994 , fCurrClipPoint(0)
995 , fPrevUmbraOutside(false)
996 , fFirstUmbraOutside(false)
997 , fValidUmbra(true) {
998
999 // make sure we're not below the canvas plane
1000 if (this->setZOffset(path.getBounds(), ctm.hasPerspective())) {
1001 // Adjust light height and radius
1002 fLightRadius *= (fLightZ + fZOffset) / fLightZ;
1003 fLightZ += fZOffset;
1004 }
Jim Van Verthb4366552017-03-27 14:25:29 -04001005
1006 // Set radius and colors
Jim Van Verthb4366552017-03-27 14:25:29 -04001007 SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY());
Jim Van Verthe308a122017-05-08 14:19:30 -04001008 SkScalar occluderHeight = this->heightFunc(center.fX, center.fY) + fZOffset;
Jim Van Verth060d9822017-05-04 09:58:17 -04001009 fUmbraColor = SkColorSetARGB(255, 0, 0, 0);
1010 fPenumbraColor = SkColorSetARGB(0, 0, 0, 0);
Jim Van Verthb4366552017-03-27 14:25:29 -04001011
Jim Van Verth1af03d42017-07-31 09:34:58 -04001012 // Compute the blur radius, scale and translation for the spot shadow.
1013 SkScalar radius;
Jim Van Verthda965502017-04-11 15:29:14 -04001014 SkMatrix shadowTransform;
1015 if (!ctm.hasPerspective()) {
Jim Van Verth1af03d42017-07-31 09:34:58 -04001016 SkScalar scale;
1017 SkVector translate;
1018 SkDrawShadowMetrics::GetSpotParams(occluderHeight, lightPos.fX, lightPos.fY, fLightZ,
1019 lightRadius, &radius, &scale, &translate);
Jim Van Verthda965502017-04-11 15:29:14 -04001020 shadowTransform.setScaleTranslate(scale, scale, translate.fX, translate.fY);
1021 } else {
1022 // For perspective, we have a scale, a z-shear, and another projective divide --
1023 // this varies at each point so we can't use an affine transform.
1024 // We'll just apply this to each generated point in turn.
1025 shadowTransform.reset();
1026 // Also can't cull the center (for now).
1027 fTransparent = true;
Jim Van Verth1af03d42017-07-31 09:34:58 -04001028 radius = SkDrawShadowMetrics::SpotBlurRadius(occluderHeight, lightPos.fZ, lightRadius);
Jim Van Verthda965502017-04-11 15:29:14 -04001029 }
Jim Van Verth1af03d42017-07-31 09:34:58 -04001030 fRadius = radius;
Jim Van Verthda965502017-04-11 15:29:14 -04001031 SkMatrix fullTransform = SkMatrix::Concat(shadowTransform, ctm);
1032
1033 // Set up our reverse mapping
Jim Van Verth7c8008c2018-02-07 15:02:50 -05001034 if (!this->setTransformedHeightFunc(fullTransform)) {
1035 return;
1036 }
Jim Van Verthb4366552017-03-27 14:25:29 -04001037
Brian Salomonab664fa2017-03-24 16:07:20 +00001038 // TODO: calculate these reserves better
Brian Salomon66085ed2017-02-02 15:39:34 -05001039 // Penumbra ring: 3*numPts
1040 // Umbra ring: numPts
Jim Van Verth91af7272017-01-27 14:15:54 -05001041 // Inner ring: numPts
Brian Salomon66085ed2017-02-02 15:39:34 -05001042 fPositions.setReserve(5 * path.countPoints());
1043 fColors.setReserve(5 * path.countPoints());
1044 // Penumbra ring: 12*numPts
1045 // Umbra ring: 3*numPts
1046 fIndices.setReserve(15 * path.countPoints());
Brian Salomone7c85c42017-03-24 16:00:35 +00001047 fClipPolygon.setReserve(path.countPoints());
Brian Salomonab664fa2017-03-24 16:07:20 +00001048
1049 // compute rough clip bounds for umbra, plus offset polygon, plus centroid
Jim Van Verthda965502017-04-11 15:29:14 -04001050 this->computeClipAndPathPolygons(path, ctm, shadowTransform);
Brian Salomonab664fa2017-03-24 16:07:20 +00001051 if (fClipPolygon.count() < 3 || fPathPolygon.count() < 3) {
Brian Salomon66085ed2017-02-02 15:39:34 -05001052 return;
1053 }
Brian Salomon66085ed2017-02-02 15:39:34 -05001054
Jim Van Verthf507c282018-05-11 10:48:20 -04001055 // compute vectors for clip tests
1056 this->computeClipVectorsAndTestCentroid();
1057
Brian Salomonab664fa2017-03-24 16:07:20 +00001058 // check to see if umbra collapses
Jim Van Verthf507c282018-05-11 10:48:20 -04001059 if (fIsConvex) {
Jim Van Verth872da6b2018-04-10 11:24:11 -04001060 SkScalar minDistSq = SkPointPriv::DistanceToLineSegmentBetweenSqd(fCentroid,
1061 fPathPolygon[0],
1062 fPathPolygon[1]);
1063 SkRect bounds;
1064 bounds.setBounds(&fPathPolygon[0], fPathPolygon.count());
1065 for (int i = 1; i < fPathPolygon.count(); ++i) {
1066 int j = i + 1;
1067 if (i == fPathPolygon.count() - 1) {
1068 j = 0;
1069 }
1070 SkPoint currPoint = fPathPolygon[i];
1071 SkPoint nextPoint = fPathPolygon[j];
1072 SkScalar distSq = SkPointPriv::DistanceToLineSegmentBetweenSqd(fCentroid, currPoint,
1073 nextPoint);
1074 if (distSq < minDistSq) {
1075 minDistSq = distSq;
1076 }
Brian Salomonab664fa2017-03-24 16:07:20 +00001077 }
Jim Van Verth872da6b2018-04-10 11:24:11 -04001078 static constexpr auto kTolerance = 1.0e-2f;
1079 if (minDistSq < (radius + kTolerance)*(radius + kTolerance)) {
1080 // if the umbra would collapse, we back off a bit on inner blur and adjust the alpha
1081 SkScalar newRadius = SkScalarSqrt(minDistSq) - kTolerance;
1082 fOffsetAdjust = newRadius - radius;
1083 SkScalar ratio = 128 * (newRadius + radius) / radius;
1084 // they aren't PMColors, but the interpolation algorithm is the same
1085 fUmbraColor = SkPMLerp(fUmbraColor, fPenumbraColor, (unsigned)ratio);
1086 radius = newRadius;
Brian Salomonab664fa2017-03-24 16:07:20 +00001087 }
1088 }
Jim Van Verth91af7272017-01-27 14:15:54 -05001089
Jim Van Verthf507c282018-05-11 10:48:20 -04001090 if (fIsConvex) {
Jim Van Verth8760e2f2018-06-12 14:21:38 -04001091 fSucceeded = this->computeConvexShadow(radius);
Jim Van Verth872da6b2018-04-10 11:24:11 -04001092 } else {
Jim Van Verth8760e2f2018-06-12 14:21:38 -04001093 fSucceeded = this->computeConcaveShadow(radius);
1094 }
1095
1096 if (!fSucceeded) {
Jim Van Verthf507c282018-05-11 10:48:20 -04001097 return;
Jim Van Verth91af7272017-01-27 14:15:54 -05001098 }
Jim Van Verthda965502017-04-11 15:29:14 -04001099
1100 if (ctm.hasPerspective()) {
1101 for (int i = 0; i < fPositions.count(); ++i) {
1102 SkScalar pathZ = fTransformedHeightFunc(fPositions[i]);
1103 SkScalar factor = SkScalarInvert(fLightZ - pathZ);
1104 fPositions[i].fX = (fPositions[i].fX*fLightZ - lightPos.fX*pathZ)*factor;
1105 fPositions[i].fY = (fPositions[i].fY*fLightZ - lightPos.fY*pathZ)*factor;
1106 }
1107#ifdef DRAW_CENTROID
1108 SkScalar pathZ = fTransformedHeightFunc(fCentroid);
1109 SkScalar factor = SkScalarInvert(fLightZ - pathZ);
1110 fCentroid.fX = (fCentroid.fX*fLightZ - lightPos.fX*pathZ)*factor;
1111 fCentroid.fY = (fCentroid.fY*fLightZ - lightPos.fY*pathZ)*factor;
1112#endif
1113 }
1114#ifdef DRAW_CENTROID
1115 *fPositions.push() = fCentroid + SkVector::Make(-2, -2);
1116 *fColors.push() = SkColorSetARGB(255, 0, 255, 255);
1117 *fPositions.push() = fCentroid + SkVector::Make(2, -2);
1118 *fColors.push() = SkColorSetARGB(255, 0, 255, 255);
1119 *fPositions.push() = fCentroid + SkVector::Make(-2, 2);
1120 *fColors.push() = SkColorSetARGB(255, 0, 255, 255);
1121 *fPositions.push() = fCentroid + SkVector::Make(2, 2);
1122 *fColors.push() = SkColorSetARGB(255, 0, 255, 255);
1123
Jim Van Verth872da6b2018-04-10 11:24:11 -04001124 this->appendQuad(fPositions.count() - 2, fPositions.count() - 1,
1125 fPositions.count() - 4, fPositions.count() - 3);
Jim Van Verthda965502017-04-11 15:29:14 -04001126#endif
1127
Brian Salomon0dda9cb2017-02-03 10:33:25 -05001128 fSucceeded = true;
Jim Van Verth91af7272017-01-27 14:15:54 -05001129}
1130
Jim Van Verthf507c282018-05-11 10:48:20 -04001131void SkSpotShadowTessellator::addToClip(const SkPoint& point) {
1132 if (fClipPolygon.isEmpty() || !duplicate_pt(point, fClipPolygon[fClipPolygon.count()-1])) {
1133 *fClipPolygon.push() = point;
1134 }
1135}
1136
Brian Salomonab664fa2017-03-24 16:07:20 +00001137void SkSpotShadowTessellator::computeClipAndPathPolygons(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthda965502017-04-11 15:29:14 -04001138 const SkMatrix& shadowTransform) {
Brian Salomonab664fa2017-03-24 16:07:20 +00001139
1140 fPathPolygon.setReserve(path.countPoints());
1141
1142 // Walk around the path and compute clip polygon and path polygon.
1143 // Will also accumulate sum of areas for centroid.
1144 // For Bezier curves, we compute additional interior points on curve.
Jim Van Verth91af7272017-01-27 14:15:54 -05001145 SkPath::Iter iter(path, true);
1146 SkPoint pts[4];
1147 SkPath::Verb verb;
1148
Jim Van Verth91af7272017-01-27 14:15:54 -05001149 fClipPolygon.reset();
1150
Brian Salomon66085ed2017-02-02 15:39:34 -05001151 // coefficients to compute cubic Bezier at t = 5/16
Brian Salomonab664fa2017-03-24 16:07:20 +00001152 static constexpr SkScalar kA = 0.32495117187f;
1153 static constexpr SkScalar kB = 0.44311523437f;
1154 static constexpr SkScalar kC = 0.20141601562f;
1155 static constexpr SkScalar kD = 0.03051757812f;
Brian Salomon66085ed2017-02-02 15:39:34 -05001156
1157 SkPoint curvePoint;
1158 SkScalar w;
Jim Van Verth91af7272017-01-27 14:15:54 -05001159 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1160 switch (verb) {
Jim Van Verth91af7272017-01-27 14:15:54 -05001161 case SkPath::kLine_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -05001162 ctm.mapPoints(&pts[1], 1);
Jim Van Verthf507c282018-05-11 10:48:20 -04001163 this->addToClip(pts[1]);
Jim Van Verthcdaf6612018-06-05 15:21:13 -04001164 this->handleLine(shadowTransform, &pts[1]);
Jim Van Verth91af7272017-01-27 14:15:54 -05001165 break;
1166 case SkPath::kQuad_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -05001167 ctm.mapPoints(pts, 3);
Brian Salomon66085ed2017-02-02 15:39:34 -05001168 // point at t = 1/2
1169 curvePoint.fX = 0.25f*pts[0].fX + 0.5f*pts[1].fX + 0.25f*pts[2].fX;
1170 curvePoint.fY = 0.25f*pts[0].fY + 0.5f*pts[1].fY + 0.25f*pts[2].fY;
Jim Van Verthf507c282018-05-11 10:48:20 -04001171 this->addToClip(curvePoint);
1172 this->addToClip(pts[2]);
Brian Salomonab664fa2017-03-24 16:07:20 +00001173 this->handleQuad(shadowTransform, pts);
Jim Van Verth91af7272017-01-27 14:15:54 -05001174 break;
1175 case SkPath::kConic_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -05001176 ctm.mapPoints(pts, 3);
Brian Salomon66085ed2017-02-02 15:39:34 -05001177 w = iter.conicWeight();
Jim Van Vertha84898d2017-02-06 13:38:23 -05001178 // point at t = 1/2
Brian Salomon66085ed2017-02-02 15:39:34 -05001179 curvePoint.fX = 0.25f*pts[0].fX + w*0.5f*pts[1].fX + 0.25f*pts[2].fX;
1180 curvePoint.fY = 0.25f*pts[0].fY + w*0.5f*pts[1].fY + 0.25f*pts[2].fY;
1181 curvePoint *= SkScalarInvert(0.5f + 0.5f*w);
Jim Van Verthf507c282018-05-11 10:48:20 -04001182 this->addToClip(curvePoint);
1183 this->addToClip(pts[2]);
Brian Salomonab664fa2017-03-24 16:07:20 +00001184 this->handleConic(shadowTransform, pts, w);
Jim Van Verth91af7272017-01-27 14:15:54 -05001185 break;
1186 case SkPath::kCubic_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -05001187 ctm.mapPoints(pts, 4);
Brian Salomon66085ed2017-02-02 15:39:34 -05001188 // point at t = 5/16
1189 curvePoint.fX = kA*pts[0].fX + kB*pts[1].fX + kC*pts[2].fX + kD*pts[3].fX;
1190 curvePoint.fY = kA*pts[0].fY + kB*pts[1].fY + kC*pts[2].fY + kD*pts[3].fY;
Jim Van Verthf507c282018-05-11 10:48:20 -04001191 this->addToClip(curvePoint);
Brian Salomon66085ed2017-02-02 15:39:34 -05001192 // point at t = 11/16
1193 curvePoint.fX = kD*pts[0].fX + kC*pts[1].fX + kB*pts[2].fX + kA*pts[3].fX;
1194 curvePoint.fY = kD*pts[0].fY + kC*pts[1].fY + kB*pts[2].fY + kA*pts[3].fY;
Jim Van Verthf507c282018-05-11 10:48:20 -04001195 this->addToClip(curvePoint);
1196 this->addToClip(pts[3]);
Brian Salomonab664fa2017-03-24 16:07:20 +00001197 this->handleCubic(shadowTransform, pts);
Jim Van Verth91af7272017-01-27 14:15:54 -05001198 break;
Brian Salomonab664fa2017-03-24 16:07:20 +00001199 case SkPath::kMove_Verb:
Jim Van Verth91af7272017-01-27 14:15:54 -05001200 case SkPath::kClose_Verb:
Brian Salomonab664fa2017-03-24 16:07:20 +00001201 case SkPath::kDone_Verb:
Jim Van Verth91af7272017-01-27 14:15:54 -05001202 break;
1203 default:
1204 SkDEBUGFAIL("unknown verb");
1205 }
1206 }
1207
Jim Van Verthcdaf6612018-06-05 15:21:13 -04001208 this->finishPathPolygon();
Brian Salomonab664fa2017-03-24 16:07:20 +00001209 fCurrClipPoint = fClipPolygon.count() - 1;
Brian Salomon66085ed2017-02-02 15:39:34 -05001210}
1211
Brian Salomonab664fa2017-03-24 16:07:20 +00001212void SkSpotShadowTessellator::computeClipVectorsAndTestCentroid() {
Brian Salomon66085ed2017-02-02 15:39:34 -05001213 SkASSERT(fClipPolygon.count() >= 3);
Brian Salomon66085ed2017-02-02 15:39:34 -05001214
Brian Salomonab664fa2017-03-24 16:07:20 +00001215 // init clip vectors
Brian Salomon66085ed2017-02-02 15:39:34 -05001216 SkVector v0 = fClipPolygon[1] - fClipPolygon[0];
Jim Van Verthf507c282018-05-11 10:48:20 -04001217 SkVector v1 = fClipPolygon[2] - fClipPolygon[0];
Brian Salomon66085ed2017-02-02 15:39:34 -05001218 *fClipVectors.push() = v0;
Brian Salomon66085ed2017-02-02 15:39:34 -05001219
1220 // init centroid check
1221 bool hiddenCentroid = true;
Jim Van Verthf507c282018-05-11 10:48:20 -04001222 v1 = fCentroid - fClipPolygon[0];
Brian Salomon66085ed2017-02-02 15:39:34 -05001223 SkScalar initCross = v0.cross(v1);
1224
1225 for (int p = 1; p < fClipPolygon.count(); ++p) {
Brian Salomonab664fa2017-03-24 16:07:20 +00001226 // add to clip vectors
Brian Salomon66085ed2017-02-02 15:39:34 -05001227 v0 = fClipPolygon[(p + 1) % fClipPolygon.count()] - fClipPolygon[p];
1228 *fClipVectors.push() = v0;
Brian Salomon66085ed2017-02-02 15:39:34 -05001229 // Determine if transformed centroid is inside clipPolygon.
Brian Salomonab664fa2017-03-24 16:07:20 +00001230 v1 = fCentroid - fClipPolygon[p];
Brian Salomon66085ed2017-02-02 15:39:34 -05001231 if (initCross*v0.cross(v1) <= 0) {
1232 hiddenCentroid = false;
1233 }
1234 }
1235 SkASSERT(fClipVectors.count() == fClipPolygon.count());
1236
Brian Salomonab664fa2017-03-24 16:07:20 +00001237 fTransparent = fTransparent || !hiddenCentroid;
Brian Salomon66085ed2017-02-02 15:39:34 -05001238}
1239
1240bool SkSpotShadowTessellator::clipUmbraPoint(const SkPoint& umbraPoint, const SkPoint& centroid,
1241 SkPoint* clipPoint) {
1242 SkVector segmentVector = centroid - umbraPoint;
1243
Brian Salomonab664fa2017-03-24 16:07:20 +00001244 int startClipPoint = fCurrClipPoint;
Brian Salomon66085ed2017-02-02 15:39:34 -05001245 do {
Brian Salomonab664fa2017-03-24 16:07:20 +00001246 SkVector dp = umbraPoint - fClipPolygon[fCurrClipPoint];
1247 SkScalar denom = fClipVectors[fCurrClipPoint].cross(segmentVector);
Brian Salomon66085ed2017-02-02 15:39:34 -05001248 SkScalar t_num = dp.cross(segmentVector);
1249 // if line segments are nearly parallel
1250 if (SkScalarNearlyZero(denom)) {
1251 // and collinear
1252 if (SkScalarNearlyZero(t_num)) {
1253 return false;
1254 }
1255 // otherwise are separate, will try the next poly segment
1256 // else if crossing lies within poly segment
1257 } else if (t_num >= 0 && t_num <= denom) {
Brian Salomonab664fa2017-03-24 16:07:20 +00001258 SkScalar s_num = dp.cross(fClipVectors[fCurrClipPoint]);
Brian Salomon66085ed2017-02-02 15:39:34 -05001259 // if umbra point is inside the clip polygon
Jim Van Verthda965502017-04-11 15:29:14 -04001260 if (s_num >= 0 && s_num <= denom) {
Brian Salomon66085ed2017-02-02 15:39:34 -05001261 segmentVector *= s_num/denom;
1262 *clipPoint = umbraPoint + segmentVector;
1263 return true;
1264 }
1265 }
Brian Salomonab664fa2017-03-24 16:07:20 +00001266 fCurrClipPoint = (fCurrClipPoint + 1) % fClipPolygon.count();
1267 } while (fCurrClipPoint != startClipPoint);
Brian Salomon66085ed2017-02-02 15:39:34 -05001268
1269 return false;
Jim Van Verth91af7272017-01-27 14:15:54 -05001270}
1271
Brian Salomonab664fa2017-03-24 16:07:20 +00001272int SkSpotShadowTessellator::getClosestUmbraPoint(const SkPoint& p) {
Cary Clarkdf429f32017-11-08 11:44:31 -05001273 SkScalar minDistance = SkPointPriv::DistanceToSqd(p, fUmbraPolygon[fCurrUmbraPoint]);
Brian Salomonab664fa2017-03-24 16:07:20 +00001274 int index = fCurrUmbraPoint;
1275 int dir = 1;
1276 int next = (index + dir) % fUmbraPolygon.count();
1277
1278 // init travel direction
Cary Clarkdf429f32017-11-08 11:44:31 -05001279 SkScalar distance = SkPointPriv::DistanceToSqd(p, fUmbraPolygon[next]);
Brian Salomonab664fa2017-03-24 16:07:20 +00001280 if (distance < minDistance) {
1281 index = next;
1282 minDistance = distance;
1283 } else {
1284 dir = fUmbraPolygon.count()-1;
1285 }
1286
1287 // iterate until we find a point that increases the distance
1288 next = (index + dir) % fUmbraPolygon.count();
Cary Clarkdf429f32017-11-08 11:44:31 -05001289 distance = SkPointPriv::DistanceToSqd(p, fUmbraPolygon[next]);
Brian Salomonab664fa2017-03-24 16:07:20 +00001290 while (distance < minDistance) {
1291 index = next;
1292 minDistance = distance;
1293 next = (index + dir) % fUmbraPolygon.count();
Cary Clarkdf429f32017-11-08 11:44:31 -05001294 distance = SkPointPriv::DistanceToSqd(p, fUmbraPolygon[next]);
Brian Salomonab664fa2017-03-24 16:07:20 +00001295 }
1296
1297 fCurrUmbraPoint = index;
1298 return index;
1299}
1300
Jim Van Verth872da6b2018-04-10 11:24:11 -04001301bool SkSpotShadowTessellator::computeConvexShadow(SkScalar radius) {
1302 // generate inner ring
1303 if (!SkInsetConvexPolygon(&fPathPolygon[0], fPathPolygon.count(), radius,
1304 &fUmbraPolygon)) {
1305 // this shouldn't happen, but just in case we'll inset using the centroid
1306 fValidUmbra = false;
1307 }
1308
1309 // walk around the path polygon, generate outer ring and connect to inner ring
1310 if (fTransparent) {
1311 *fPositions.push() = fCentroid;
1312 *fColors.push() = fUmbraColor;
1313 }
1314 fCurrUmbraPoint = 0;
Jim Van Verth872da6b2018-04-10 11:24:11 -04001315
Jim Van Verth7deacf42018-06-08 15:13:25 -04001316 // initial setup
1317 // add first quad
1318 int polyCount = fPathPolygon.count();
1319 if (!compute_normal(fPathPolygon[polyCount-1], fPathPolygon[0], fDirection, &fFirstOutset)) {
1320 // polygon should be sanitized by this point, so this is unrecoverable
Jim Van Verth872da6b2018-04-10 11:24:11 -04001321 return false;
1322 }
1323
Jim Van Verth7deacf42018-06-08 15:13:25 -04001324 fFirstOutset *= fRadius;
1325 fFirstPoint = fPathPolygon[polyCount - 1];
1326 fFirstVertexIndex = fPositions.count();
1327 fPrevOutset = fFirstOutset;
1328 fPrevPoint = fFirstPoint;
1329 fPrevUmbraIndex = -1;
Jim Van Verth872da6b2018-04-10 11:24:11 -04001330
Jim Van Verth7deacf42018-06-08 15:13:25 -04001331 this->addInnerPoint(fFirstPoint, &fPrevUmbraIndex);
1332
1333 if (!fTransparent) {
1334 SkPoint clipPoint;
1335 bool isOutside = this->clipUmbraPoint(fPositions[fFirstVertexIndex],
1336 fCentroid, &clipPoint);
1337 if (isOutside) {
1338 *fPositions.push() = clipPoint;
1339 *fColors.push() = fUmbraColor;
Jim Van Verth872da6b2018-04-10 11:24:11 -04001340 }
Jim Van Verth7deacf42018-06-08 15:13:25 -04001341 fPrevUmbraOutside = isOutside;
1342 fFirstUmbraOutside = isOutside;
Jim Van Verth872da6b2018-04-10 11:24:11 -04001343 }
1344
Jim Van Verth7deacf42018-06-08 15:13:25 -04001345 SkPoint newPoint = fFirstPoint + fFirstOutset;
1346 *fPositions.push() = newPoint;
1347 *fColors.push() = fPenumbraColor;
1348 this->addEdge(fPathPolygon[0], fFirstOutset, false);
1349
1350 for (int i = 1; i < polyCount; ++i) {
1351 if (!this->handlePolyPoint(fPathPolygon[i], i == polyCount-1)) {
1352 return false;
1353 }
1354 }
1355 SkASSERT(this->indexCount());
1356
Jim Van Verth872da6b2018-04-10 11:24:11 -04001357 // final fan
Jim Van Verth7deacf42018-06-08 15:13:25 -04001358 SkASSERT(fPositions.count() >= 3);
1359 if (this->addArc(fFirstOutset, false)) {
1360 if (fFirstUmbraOutside) {
1361 this->appendTriangle(fFirstVertexIndex, fPositions.count() - 1,
1362 fFirstVertexIndex + 2);
Jim Van Verth872da6b2018-04-10 11:24:11 -04001363 } else {
Jim Van Verth7deacf42018-06-08 15:13:25 -04001364 this->appendTriangle(fFirstVertexIndex, fPositions.count() - 1,
1365 fFirstVertexIndex + 1);
1366 }
1367 } else {
1368 // no arc added, fix up by setting first penumbra point position to last one
1369 if (fFirstUmbraOutside) {
1370 fPositions[fFirstVertexIndex + 2] = fPositions[fPositions.count() - 1];
1371 } else {
1372 fPositions[fFirstVertexIndex + 1] = fPositions[fPositions.count() - 1];
Jim Van Verth872da6b2018-04-10 11:24:11 -04001373 }
1374 }
1375
1376 return true;
1377}
1378
1379bool SkSpotShadowTessellator::computeConcaveShadow(SkScalar radius) {
1380 // TODO: remove when we support filling the penumbra
1381 if (fTransparent) {
1382 return false;
1383 }
1384
1385 // generate inner ring
1386 SkTDArray<int> umbraIndices;
1387 umbraIndices.setReserve(fPathPolygon.count());
1388 if (!SkOffsetSimplePolygon(&fPathPolygon[0], fPathPolygon.count(), radius,
1389 &fUmbraPolygon, &umbraIndices)) {
1390 // TODO: figure out how to handle this case
1391 return false;
1392 }
1393
1394 // generate outer ring
1395 SkTDArray<SkPoint> penumbraPolygon;
1396 SkTDArray<int> penumbraIndices;
1397 penumbraPolygon.setReserve(fUmbraPolygon.count());
1398 penumbraIndices.setReserve(fUmbraPolygon.count());
1399 if (!SkOffsetSimplePolygon(&fPathPolygon[0], fPathPolygon.count(), -radius,
1400 &penumbraPolygon, &penumbraIndices)) {
1401 // TODO: figure out how to handle this case
1402 return false;
1403 }
1404
1405 if (!fUmbraPolygon.count() || !penumbraPolygon.count()) {
1406 return false;
1407 }
1408
1409 // attach the rings together
Jim Van Verth8760e2f2018-06-12 14:21:38 -04001410 this->stitchConcaveRings(fUmbraPolygon, &umbraIndices, penumbraPolygon, &penumbraIndices);
Jim Van Verth872da6b2018-04-10 11:24:11 -04001411
1412 return true;
1413}
1414
Jim Van Verthefe3ded2017-01-30 13:11:45 -05001415void SkSpotShadowTessellator::mapPoints(SkScalar scale, const SkVector& xlate,
Jim Van Verth91af7272017-01-27 14:15:54 -05001416 SkPoint* pts, int count) {
1417 // TODO: vectorize
1418 for (int i = 0; i < count; ++i) {
1419 pts[i] *= scale;
1420 pts[i] += xlate;
1421 }
1422}
1423
Jim Van Verth7deacf42018-06-08 15:13:25 -04001424bool SkSpotShadowTessellator::handlePolyPoint(const SkPoint& p, bool lastPoint) {
Jim Van Verth91af7272017-01-27 14:15:54 -05001425 SkVector normal;
Jim Van Verthda965502017-04-11 15:29:14 -04001426 if (compute_normal(fPrevPoint, p, fDirection, &normal)) {
1427 normal *= fRadius;
1428 this->addArc(normal, true);
Jim Van Verth7deacf42018-06-08 15:13:25 -04001429 this->addEdge(p, normal, lastPoint);
Jim Van Verth91af7272017-01-27 14:15:54 -05001430 }
Jim Van Verthb55eb282017-07-18 14:13:45 -04001431
1432 return true;
Jim Van Verth91af7272017-01-27 14:15:54 -05001433}
1434
Jim Van Verth8c2de8f2018-05-14 11:20:58 -04001435bool SkSpotShadowTessellator::addInnerPoint(const SkPoint& pathPoint, int* currUmbraIndex) {
Brian Salomonab664fa2017-03-24 16:07:20 +00001436 SkPoint umbraPoint;
1437 if (!fValidUmbra) {
1438 SkVector v = fCentroid - pathPoint;
1439 v *= 0.95f;
1440 umbraPoint = pathPoint + v;
Jim Van Verth91af7272017-01-27 14:15:54 -05001441 } else {
Brian Salomonab664fa2017-03-24 16:07:20 +00001442 umbraPoint = fUmbraPolygon[this->getClosestUmbraPoint(pathPoint)];
Jim Van Verth91af7272017-01-27 14:15:54 -05001443 }
Brian Salomon66085ed2017-02-02 15:39:34 -05001444
Jim Van Verth91af7272017-01-27 14:15:54 -05001445 fPrevPoint = pathPoint;
Brian Salomonab664fa2017-03-24 16:07:20 +00001446
1447 // merge "close" points
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -04001448 if (fPrevUmbraIndex == -1 ||
Brian Salomonab664fa2017-03-24 16:07:20 +00001449 !duplicate_pt(umbraPoint, fPositions[fPrevUmbraIndex])) {
Jim Van Verth8c2de8f2018-05-14 11:20:58 -04001450 // if we've wrapped around, don't add a new point
1451 if (fPrevUmbraIndex >= 0 && duplicate_pt(umbraPoint, fPositions[fFirstVertexIndex])) {
1452 *currUmbraIndex = fFirstVertexIndex;
1453 } else {
1454 *currUmbraIndex = fPositions.count();
1455 *fPositions.push() = umbraPoint;
1456 *fColors.push() = fUmbraColor;
1457 }
Brian Salomonab664fa2017-03-24 16:07:20 +00001458 return false;
1459 } else {
Jim Van Verth8c2de8f2018-05-14 11:20:58 -04001460 *currUmbraIndex = fPrevUmbraIndex;
Brian Salomonab664fa2017-03-24 16:07:20 +00001461 return true;
1462 }
Jim Van Verth91af7272017-01-27 14:15:54 -05001463}
1464
Jim Van Verth7deacf42018-06-08 15:13:25 -04001465void SkSpotShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVector& nextNormal,
1466 bool lastEdge) {
Brian Salomon66085ed2017-02-02 15:39:34 -05001467 // add next umbra point
Jim Van Verth8c2de8f2018-05-14 11:20:58 -04001468 int currUmbraIndex;
Jim Van Verth7deacf42018-06-08 15:13:25 -04001469 bool duplicate;
1470 if (lastEdge) {
1471 duplicate = false;
1472 currUmbraIndex = fFirstVertexIndex;
1473 fPrevPoint = nextPoint;
1474 } else {
1475 duplicate = this->addInnerPoint(nextPoint, &currUmbraIndex);
1476 }
Jim Van Verth8c2de8f2018-05-14 11:20:58 -04001477 int prevPenumbraIndex = duplicate || (currUmbraIndex == fFirstVertexIndex)
1478 ? fPositions.count()-1
1479 : fPositions.count()-2;
Brian Salomonab664fa2017-03-24 16:07:20 +00001480 if (!duplicate) {
1481 // add to center fan if transparent or centroid showing
1482 if (fTransparent) {
Jim Van Verth872da6b2018-04-10 11:24:11 -04001483 this->appendTriangle(0, fPrevUmbraIndex, currUmbraIndex);
Brian Salomonab664fa2017-03-24 16:07:20 +00001484 // otherwise add to clip ring
1485 } else {
Brian Salomon66085ed2017-02-02 15:39:34 -05001486 SkPoint clipPoint;
Jim Van Verth7deacf42018-06-08 15:13:25 -04001487 bool isOutside = lastEdge ? fFirstUmbraOutside
1488 : this->clipUmbraPoint(fPositions[currUmbraIndex], fCentroid,
1489 &clipPoint);
Brian Salomon66085ed2017-02-02 15:39:34 -05001490 if (isOutside) {
Jim Van Verth7deacf42018-06-08 15:13:25 -04001491 if (!lastEdge) {
1492 *fPositions.push() = clipPoint;
1493 *fColors.push() = fUmbraColor;
1494 }
Jim Van Verth872da6b2018-04-10 11:24:11 -04001495 this->appendTriangle(fPrevUmbraIndex, currUmbraIndex, currUmbraIndex + 1);
Brian Salomon66085ed2017-02-02 15:39:34 -05001496 if (fPrevUmbraOutside) {
1497 // fill out quad
Jim Van Verth872da6b2018-04-10 11:24:11 -04001498 this->appendTriangle(fPrevUmbraIndex, currUmbraIndex + 1,
1499 fPrevUmbraIndex + 1);
Brian Salomon66085ed2017-02-02 15:39:34 -05001500 }
1501 } else if (fPrevUmbraOutside) {
1502 // add tri
Jim Van Verth872da6b2018-04-10 11:24:11 -04001503 this->appendTriangle(fPrevUmbraIndex, currUmbraIndex, fPrevUmbraIndex + 1);
Brian Salomon66085ed2017-02-02 15:39:34 -05001504 }
Jim Van Verth872da6b2018-04-10 11:24:11 -04001505
Brian Salomon66085ed2017-02-02 15:39:34 -05001506 fPrevUmbraOutside = isOutside;
1507 }
1508 }
1509
1510 // add next penumbra point and quad
Jim Van Verth91af7272017-01-27 14:15:54 -05001511 SkPoint newPoint = nextPoint + nextNormal;
1512 *fPositions.push() = newPoint;
1513 *fColors.push() = fPenumbraColor;
1514
Brian Salomonab664fa2017-03-24 16:07:20 +00001515 if (!duplicate) {
Jim Van Verth872da6b2018-04-10 11:24:11 -04001516 this->appendTriangle(fPrevUmbraIndex, prevPenumbraIndex, currUmbraIndex);
Brian Salomonab664fa2017-03-24 16:07:20 +00001517 }
Jim Van Verth872da6b2018-04-10 11:24:11 -04001518 this->appendTriangle(prevPenumbraIndex, fPositions.count() - 1, currUmbraIndex);
Jim Van Verth91af7272017-01-27 14:15:54 -05001519
Brian Salomon66085ed2017-02-02 15:39:34 -05001520 fPrevUmbraIndex = currUmbraIndex;
Jim Van Verth76387852017-05-16 16:55:16 -04001521 fPrevOutset = nextNormal;
Jim Van Verth91af7272017-01-27 14:15:54 -05001522}
Brian Salomon958fbc42017-01-30 17:01:28 -05001523
1524///////////////////////////////////////////////////////////////////////////////////////////////////
1525
Brian Salomonaff27a22017-02-06 15:47:44 -05001526sk_sp<SkVertices> SkShadowTessellator::MakeAmbient(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -04001527 const SkPoint3& zPlane, bool transparent) {
Mike Reed27575e82018-05-17 10:11:31 -04001528 if (!ctm.mapRect(path.getBounds()).isFinite() || !zPlane.isFinite()) {
Mike Reed9d5c6742018-03-06 10:31:27 -05001529 return nullptr;
1530 }
Jim Van Verthe308a122017-05-08 14:19:30 -04001531 SkAmbientShadowTessellator ambientTess(path, ctm, zPlane, transparent);
Brian Salomonaff27a22017-02-06 15:47:44 -05001532 return ambientTess.releaseVertices();
Brian Salomon958fbc42017-01-30 17:01:28 -05001533}
1534
Brian Salomonaff27a22017-02-06 15:47:44 -05001535sk_sp<SkVertices> SkShadowTessellator::MakeSpot(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -04001536 const SkPoint3& zPlane, const SkPoint3& lightPos,
Jim Van Verth060d9822017-05-04 09:58:17 -04001537 SkScalar lightRadius, bool transparent) {
Mike Reed27575e82018-05-17 10:11:31 -04001538 if (!ctm.mapRect(path.getBounds()).isFinite() || !zPlane.isFinite() ||
Jim Van Verth1989c492018-05-31 13:15:16 -04001539 !lightPos.isFinite() || !(lightPos.fZ >= SK_ScalarNearlyZero) ||
1540 !SkScalarIsFinite(lightRadius) || !(lightRadius >= SK_ScalarNearlyZero)) {
Mike Reed9d5c6742018-03-06 10:31:27 -05001541 return nullptr;
1542 }
Jim Van Verthe308a122017-05-08 14:19:30 -04001543 SkSpotShadowTessellator spotTess(path, ctm, zPlane, lightPos, lightRadius, transparent);
Brian Salomonaff27a22017-02-06 15:47:44 -05001544 return spotTess.releaseVertices();
Brian Salomon958fbc42017-01-30 17:01:28 -05001545}