blob: 75e40592223cc454acf05de5862cc239c184b344 [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 Vertha84898d2017-02-06 13:38:23 -050048 virtual void handleLine(const SkPoint& p) = 0;
49 void handleLine(const SkMatrix& m, SkPoint* p);
Brian Salomon958fbc42017-01-30 17:01:28 -050050
51 void handleQuad(const SkPoint pts[3]);
Jim Van Vertha84898d2017-02-06 13:38:23 -050052 void handleQuad(const SkMatrix& m, SkPoint pts[3]);
Brian Salomon958fbc42017-01-30 17:01:28 -050053
Jim Van Vertha84898d2017-02-06 13:38:23 -050054 void handleCubic(const SkMatrix& m, SkPoint pts[4]);
Brian Salomon958fbc42017-01-30 17:01:28 -050055
Jim Van Vertha84898d2017-02-06 13:38:23 -050056 void handleConic(const SkMatrix& m, SkPoint pts[3], SkScalar w);
Brian Salomon958fbc42017-01-30 17:01:28 -050057
Jim Van Verthda965502017-04-11 15:29:14 -040058 bool setTransformedHeightFunc(const SkMatrix& ctm);
Brian Salomon958fbc42017-01-30 17:01:28 -050059
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -040060 bool addArc(const SkVector& nextNormal, bool finishArc);
Jim Van Verthb4366552017-03-27 14:25:29 -040061
Jim Van Verthe308a122017-05-08 14:19:30 -040062 SkScalar heightFunc(SkScalar x, SkScalar y) {
63 return fZPlaneParams.fX*x + fZPlaneParams.fY*y + fZPlaneParams.fZ;
64 }
65
66 SkPoint3 fZPlaneParams;
Jim Van Verthda965502017-04-11 15:29:14 -040067 std::function<SkScalar(const SkPoint&)> fTransformedHeightFunc;
68 SkScalar fZOffset;
69 // members for perspective height function
Jim Van Verth4c9b8932017-05-15 13:49:21 -040070 SkPoint3 fTransformedZParams;
Jim Van Verthda965502017-04-11 15:29:14 -040071 SkScalar fPartialDeterminants[3];
72
73 // first two points
Brian Salomon958fbc42017-01-30 17:01:28 -050074 SkTDArray<SkPoint> fInitPoints;
75 // temporary buffer
76 SkTDArray<SkPoint> fPointBuffer;
Brian Salomon0dda9cb2017-02-03 10:33:25 -050077
Jim Van Vertha84898d2017-02-06 13:38:23 -050078 SkTDArray<SkPoint> fPositions;
79 SkTDArray<SkColor> fColors;
80 SkTDArray<uint16_t> fIndices;
81
Jim Van Verth76387852017-05-16 16:55:16 -040082 int fFirstVertexIndex;
83 SkVector fFirstOutset;
Jim Van Vertha84898d2017-02-06 13:38:23 -050084 SkPoint fFirstPoint;
85
Brian Salomon0dda9cb2017-02-03 10:33:25 -050086 bool fSucceeded;
Jim Van Vertha84898d2017-02-06 13:38:23 -050087 bool fTransparent;
88
89 SkColor fUmbraColor;
90 SkColor fPenumbraColor;
91
92 SkScalar fRadius;
93 SkScalar fDirection;
94 int fPrevUmbraIndex;
Jim Van Verth76387852017-05-16 16:55:16 -040095 SkVector fPrevOutset;
Jim Van Vertha84898d2017-02-06 13:38:23 -050096 SkPoint fPrevPoint;
Brian Salomon958fbc42017-01-30 17:01:28 -050097};
98
Jim Van Verthda965502017-04-11 15:29:14 -040099static bool compute_normal(const SkPoint& p0, const SkPoint& p1, SkScalar dir,
Jim Van Verthbce74962017-01-25 09:39:46 -0500100 SkVector* newNormal) {
101 SkVector normal;
102 // compute perpendicular
103 normal.fX = p0.fY - p1.fY;
104 normal.fY = p1.fX - p0.fX;
Jim Van Verthda965502017-04-11 15:29:14 -0400105 normal *= dir;
Jim Van Verthbce74962017-01-25 09:39:46 -0500106 if (!normal.normalize()) {
107 return false;
108 }
Jim Van Verthbce74962017-01-25 09:39:46 -0500109 *newNormal = normal;
110 return true;
111}
112
113static void compute_radial_steps(const SkVector& v1, const SkVector& v2, SkScalar r,
114 SkScalar* rotSin, SkScalar* rotCos, int* n) {
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400115 const SkScalar kRecipPixelsPerArcSegment = 0.125f;
Jim Van Verthbce74962017-01-25 09:39:46 -0500116
117 SkScalar rCos = v1.dot(v2);
118 SkScalar rSin = v1.cross(v2);
119 SkScalar theta = SkScalarATan2(rSin, rCos);
120
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400121 int steps = SkScalarFloorToInt(r*theta*kRecipPixelsPerArcSegment);
Jim Van Verthbce74962017-01-25 09:39:46 -0500122
123 SkScalar dTheta = theta / steps;
124 *rotSin = SkScalarSinCos(dTheta, rotCos);
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400125 *n = steps;
Jim Van Verthbce74962017-01-25 09:39:46 -0500126}
127
Jim Van Verthe308a122017-05-08 14:19:30 -0400128SkBaseShadowTessellator::SkBaseShadowTessellator(const SkPoint3& zPlaneParams, bool transparent)
129 : fZPlaneParams(zPlaneParams)
Jim Van Verthda965502017-04-11 15:29:14 -0400130 , fZOffset(0)
Jim Van Verth76387852017-05-16 16:55:16 -0400131 , fFirstVertexIndex(-1)
Brian Salomonaff27a22017-02-06 15:47:44 -0500132 , fSucceeded(false)
133 , fTransparent(transparent)
Brian Salomonaff27a22017-02-06 15:47:44 -0500134 , fDirection(1)
135 , fPrevUmbraIndex(-1) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500136 fInitPoints.setReserve(3);
137
138 // child classes will set reserve for positions, colors and indices
139}
140
Jim Van Verthda965502017-04-11 15:29:14 -0400141bool SkBaseShadowTessellator::setZOffset(const SkRect& bounds, bool perspective) {
Jim Van Verthe308a122017-05-08 14:19:30 -0400142 SkScalar minZ = this->heightFunc(bounds.fLeft, bounds.fTop);
Jim Van Verthda965502017-04-11 15:29:14 -0400143 if (perspective) {
Jim Van Verthe308a122017-05-08 14:19:30 -0400144 SkScalar z = this->heightFunc(bounds.fLeft, bounds.fBottom);
Jim Van Verthda965502017-04-11 15:29:14 -0400145 if (z < minZ) {
146 minZ = z;
147 }
Jim Van Verthe308a122017-05-08 14:19:30 -0400148 z = this->heightFunc(bounds.fRight, bounds.fTop);
Jim Van Verthda965502017-04-11 15:29:14 -0400149 if (z < minZ) {
150 minZ = z;
151 }
Jim Van Verthe308a122017-05-08 14:19:30 -0400152 z = this->heightFunc(bounds.fRight, bounds.fBottom);
Jim Van Verthda965502017-04-11 15:29:14 -0400153 if (z < minZ) {
154 minZ = z;
155 }
156 }
157
158 if (minZ < kMinHeight) {
159 fZOffset = -minZ + kMinHeight;
160 return true;
161 }
162
163 return false;
164}
165
Jim Van Vertha84898d2017-02-06 13:38:23 -0500166// tesselation tolerance values, in device space pixels
Mike Kleinb8b51e62017-02-09 15:22:53 -0500167#if SK_SUPPORT_GPU
Jim Van Vertha84898d2017-02-06 13:38:23 -0500168static const SkScalar kQuadTolerance = 0.2f;
169static const SkScalar kCubicTolerance = 0.2f;
Mike Kleinb8b51e62017-02-09 15:22:53 -0500170#endif
Jim Van Vertha84898d2017-02-06 13:38:23 -0500171static const SkScalar kConicTolerance = 0.5f;
172
Brian Salomonaff27a22017-02-06 15:47:44 -0500173void SkBaseShadowTessellator::handleLine(const SkMatrix& m, SkPoint* p) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500174 m.mapPoints(p, 1);
175 this->handleLine(*p);
176}
177
Brian Salomonaff27a22017-02-06 15:47:44 -0500178void SkBaseShadowTessellator::handleQuad(const SkPoint pts[3]) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500179#if SK_SUPPORT_GPU
Jim Van Vertha947e292018-02-26 13:54:34 -0500180 // check for degeneracy
181 SkVector v0 = pts[1] - pts[0];
182 SkVector v1 = pts[2] - pts[0];
183 if (SkScalarNearlyZero(v0.cross(v1))) {
184 return;
185 }
Jim Van Vertha84898d2017-02-06 13:38:23 -0500186 // TODO: Pull PathUtils out of Ganesh?
187 int maxCount = GrPathUtils::quadraticPointCount(pts, kQuadTolerance);
188 fPointBuffer.setReserve(maxCount);
189 SkPoint* target = fPointBuffer.begin();
190 int count = GrPathUtils::generateQuadraticPoints(pts[0], pts[1], pts[2],
191 kQuadTolerance, &target, maxCount);
192 fPointBuffer.setCount(count);
193 for (int i = 0; i < count; i++) {
194 this->handleLine(fPointBuffer[i]);
195 }
196#else
197 // for now, just to draw something
198 this->handleLine(pts[1]);
199 this->handleLine(pts[2]);
200#endif
201}
202
Brian Salomonaff27a22017-02-06 15:47:44 -0500203void SkBaseShadowTessellator::handleQuad(const SkMatrix& m, SkPoint pts[3]) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500204 m.mapPoints(pts, 3);
205 this->handleQuad(pts);
206}
207
Brian Salomonaff27a22017-02-06 15:47:44 -0500208void SkBaseShadowTessellator::handleCubic(const SkMatrix& m, SkPoint pts[4]) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500209 m.mapPoints(pts, 4);
210#if SK_SUPPORT_GPU
211 // TODO: Pull PathUtils out of Ganesh?
212 int maxCount = GrPathUtils::cubicPointCount(pts, kCubicTolerance);
213 fPointBuffer.setReserve(maxCount);
214 SkPoint* target = fPointBuffer.begin();
215 int count = GrPathUtils::generateCubicPoints(pts[0], pts[1], pts[2], pts[3],
216 kCubicTolerance, &target, maxCount);
217 fPointBuffer.setCount(count);
218 for (int i = 0; i < count; i++) {
219 this->handleLine(fPointBuffer[i]);
220 }
221#else
222 // for now, just to draw something
223 this->handleLine(pts[1]);
224 this->handleLine(pts[2]);
225 this->handleLine(pts[3]);
226#endif
227}
228
Brian Salomonaff27a22017-02-06 15:47:44 -0500229void SkBaseShadowTessellator::handleConic(const SkMatrix& m, SkPoint pts[3], SkScalar w) {
Jim Van Verthda965502017-04-11 15:29:14 -0400230 if (m.hasPerspective()) {
231 w = SkConic::TransformW(pts, w, m);
232 }
Jim Van Vertha84898d2017-02-06 13:38:23 -0500233 m.mapPoints(pts, 3);
234 SkAutoConicToQuads quadder;
235 const SkPoint* quads = quadder.computeQuads(pts, w, kConicTolerance);
236 SkPoint lastPoint = *(quads++);
237 int count = quadder.countQuads();
238 for (int i = 0; i < count; ++i) {
239 SkPoint quadPts[3];
240 quadPts[0] = lastPoint;
241 quadPts[1] = quads[0];
242 quadPts[2] = i == count - 1 ? pts[2] : quads[1];
243 this->handleQuad(quadPts);
244 lastPoint = quadPts[2];
245 quads += 2;
246 }
247}
248
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400249bool SkBaseShadowTessellator::addArc(const SkVector& nextNormal, bool finishArc) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500250 // fill in fan from previous quad
251 SkScalar rotSin, rotCos;
252 int numSteps;
Jim Van Verth76387852017-05-16 16:55:16 -0400253 compute_radial_steps(fPrevOutset, nextNormal, fRadius, &rotSin, &rotCos, &numSteps);
254 SkVector prevNormal = fPrevOutset;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400255 for (int i = 0; i < numSteps-1; ++i) {
Jim Van Verthda965502017-04-11 15:29:14 -0400256 SkVector currNormal;
257 currNormal.fX = prevNormal.fX*rotCos - prevNormal.fY*rotSin;
258 currNormal.fY = prevNormal.fY*rotCos + prevNormal.fX*rotSin;
259 *fPositions.push() = fPrevPoint + currNormal;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500260 *fColors.push() = fPenumbraColor;
261 *fIndices.push() = fPrevUmbraIndex;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500262 *fIndices.push() = fPositions.count() - 1;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400263 *fIndices.push() = fPositions.count() - 2;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500264
Jim Van Verthda965502017-04-11 15:29:14 -0400265 prevNormal = currNormal;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500266 }
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400267 if (finishArc && numSteps) {
Jim Van Verthda965502017-04-11 15:29:14 -0400268 *fPositions.push() = fPrevPoint + nextNormal;
269 *fColors.push() = fPenumbraColor;
270 *fIndices.push() = fPrevUmbraIndex;
Jim Van Verthda965502017-04-11 15:29:14 -0400271 *fIndices.push() = fPositions.count() - 1;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400272 *fIndices.push() = fPositions.count() - 2;
Jim Van Verthda965502017-04-11 15:29:14 -0400273 }
Jim Van Verth76387852017-05-16 16:55:16 -0400274 fPrevOutset = nextNormal;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400275
276 return (numSteps > 0);
Jim Van Vertha84898d2017-02-06 13:38:23 -0500277}
278
Jim Van Verthda965502017-04-11 15:29:14 -0400279bool SkBaseShadowTessellator::setTransformedHeightFunc(const SkMatrix& ctm) {
Jim Van Verth4c9b8932017-05-15 13:49:21 -0400280 if (SkScalarNearlyZero(fZPlaneParams.fX) && SkScalarNearlyZero(fZPlaneParams.fY)) {
Jim Van Verthda965502017-04-11 15:29:14 -0400281 fTransformedHeightFunc = [this](const SkPoint& p) {
Jim Van Verthe308a122017-05-08 14:19:30 -0400282 return fZPlaneParams.fZ;
Jim Van Verthda965502017-04-11 15:29:14 -0400283 };
284 } else {
285 SkMatrix ctmInverse;
Jim Van Vertha947e292018-02-26 13:54:34 -0500286 if (!ctm.invert(&ctmInverse) || !ctmInverse.isFinite()) {
Jim Van Verthda965502017-04-11 15:29:14 -0400287 return false;
288 }
Jim Van Verthda965502017-04-11 15:29:14 -0400289 // multiply by transpose
Jim Van Verth4c9b8932017-05-15 13:49:21 -0400290 fTransformedZParams = SkPoint3::Make(
Jim Van Verthe308a122017-05-08 14:19:30 -0400291 ctmInverse[SkMatrix::kMScaleX] * fZPlaneParams.fX +
292 ctmInverse[SkMatrix::kMSkewY] * fZPlaneParams.fY +
293 ctmInverse[SkMatrix::kMPersp0] * fZPlaneParams.fZ,
294
295 ctmInverse[SkMatrix::kMSkewX] * fZPlaneParams.fX +
296 ctmInverse[SkMatrix::kMScaleY] * fZPlaneParams.fY +
297 ctmInverse[SkMatrix::kMPersp1] * fZPlaneParams.fZ,
298
299 ctmInverse[SkMatrix::kMTransX] * fZPlaneParams.fX +
300 ctmInverse[SkMatrix::kMTransY] * fZPlaneParams.fY +
301 ctmInverse[SkMatrix::kMPersp2] * fZPlaneParams.fZ
302 );
Jim Van Verthda965502017-04-11 15:29:14 -0400303
Jim Van Verth4c9b8932017-05-15 13:49:21 -0400304 if (ctm.hasPerspective()) {
305 // We use Cramer's rule to solve for the W value for a given post-divide X and Y,
306 // so pre-compute those values that are independent of X and Y.
307 // W is det(ctmInverse)/(PD[0]*X + PD[1]*Y + PD[2])
308 fPartialDeterminants[0] = ctm[SkMatrix::kMSkewY] * ctm[SkMatrix::kMPersp1] -
309 ctm[SkMatrix::kMScaleY] * ctm[SkMatrix::kMPersp0];
310 fPartialDeterminants[1] = ctm[SkMatrix::kMPersp0] * ctm[SkMatrix::kMSkewX] -
311 ctm[SkMatrix::kMPersp1] * ctm[SkMatrix::kMScaleX];
312 fPartialDeterminants[2] = ctm[SkMatrix::kMScaleX] * ctm[SkMatrix::kMScaleY] -
313 ctm[SkMatrix::kMSkewX] * ctm[SkMatrix::kMSkewY];
314 SkScalar ctmDeterminant = ctm[SkMatrix::kMTransX] * fPartialDeterminants[0] +
315 ctm[SkMatrix::kMTransY] * fPartialDeterminants[1] +
316 ctm[SkMatrix::kMPersp2] * fPartialDeterminants[2];
Jim Van Verthda965502017-04-11 15:29:14 -0400317
Jim Van Verth4c9b8932017-05-15 13:49:21 -0400318 // Pre-bake the numerator of Cramer's rule into the zParams to avoid another multiply.
319 // TODO: this may introduce numerical instability, but I haven't seen any issues yet.
320 fTransformedZParams.fX *= ctmDeterminant;
321 fTransformedZParams.fY *= ctmDeterminant;
322 fTransformedZParams.fZ *= ctmDeterminant;
Jim Van Verthda965502017-04-11 15:29:14 -0400323
Jim Van Verth4c9b8932017-05-15 13:49:21 -0400324 fTransformedHeightFunc = [this](const SkPoint& p) {
325 SkScalar denom = p.fX * fPartialDeterminants[0] +
326 p.fY * fPartialDeterminants[1] +
327 fPartialDeterminants[2];
328 SkScalar w = SkScalarFastInvert(denom);
329 return fZOffset + w*(fTransformedZParams.fX * p.fX +
330 fTransformedZParams.fY * p.fY +
331 fTransformedZParams.fZ);
332 };
333 } else {
334 fTransformedHeightFunc = [this](const SkPoint& p) {
335 return fZOffset + fTransformedZParams.fX * p.fX +
336 fTransformedZParams.fY * p.fY + fTransformedZParams.fZ;
337 };
338 }
Jim Van Verthda965502017-04-11 15:29:14 -0400339 }
340
341 return true;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500342}
343
344
345//////////////////////////////////////////////////////////////////////////////////////////////////
346
Brian Salomonaff27a22017-02-06 15:47:44 -0500347class SkAmbientShadowTessellator : public SkBaseShadowTessellator {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500348public:
349 SkAmbientShadowTessellator(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -0400350 const SkPoint3& zPlaneParams, bool transparent);
Jim Van Vertha84898d2017-02-06 13:38:23 -0500351
352private:
353 void handleLine(const SkPoint& p) override;
Jim Van Verthda965502017-04-11 15:29:14 -0400354 void addEdge(const SkVector& nextPoint, const SkVector& nextNormal);
Jim Van Vertha84898d2017-02-06 13:38:23 -0500355
Jim Van Verthda965502017-04-11 15:29:14 -0400356 static constexpr auto kMaxEdgeLenSqr = 20 * 20;
Jim Van Verth76387852017-05-16 16:55:16 -0400357 static constexpr auto kInsetFactor = -0.5f;
Jim Van Verthda965502017-04-11 15:29:14 -0400358
359 SkScalar offset(SkScalar z) {
Jim Van Verth1af03d42017-07-31 09:34:58 -0400360 return SkDrawShadowMetrics::AmbientBlurRadius(z);
Jim Van Verthda965502017-04-11 15:29:14 -0400361 }
362 SkColor umbraColor(SkScalar z) {
Jim Van Verth1af03d42017-07-31 09:34:58 -0400363 SkScalar umbraAlpha = SkScalarInvert(SkDrawShadowMetrics::AmbientRecipAlpha(z));
Jim Van Verth060d9822017-05-04 09:58:17 -0400364 return SkColorSetARGB(umbraAlpha * 255.9999f, 0, 0, 0);
Jim Van Verthda965502017-04-11 15:29:14 -0400365 }
366
Jim Van Vertha84898d2017-02-06 13:38:23 -0500367 int fCentroidCount;
Jim Van Verth76387852017-05-16 16:55:16 -0400368 bool fSplitFirstEdge;
369 bool fSplitPreviousEdge;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500370
Brian Salomonaff27a22017-02-06 15:47:44 -0500371 typedef SkBaseShadowTessellator INHERITED;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500372};
373
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500374SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path,
Jim Van Vertha84898d2017-02-06 13:38:23 -0500375 const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -0400376 const SkPoint3& zPlaneParams,
Jim Van Verthbce74962017-01-25 09:39:46 -0500377 bool transparent)
Jim Van Verth76387852017-05-16 16:55:16 -0400378 : INHERITED(zPlaneParams, transparent)
379 , fSplitFirstEdge(false)
380 , fSplitPreviousEdge(false) {
Jim Van Verth22526362018-02-28 14:51:19 -0500381 // TODO: support some concave paths
Mike Reed9d5c6742018-03-06 10:31:27 -0500382 SkASSERT(path.isConvex());
Jim Van Verth22526362018-02-28 14:51:19 -0500383
Jim Van Verthda965502017-04-11 15:29:14 -0400384 // Set base colors
Jim Van Verth1af03d42017-07-31 09:34:58 -0400385 SkScalar umbraAlpha = SkScalarInvert(SkDrawShadowMetrics::AmbientRecipAlpha(heightFunc(0, 0)));
Jim Van Verthb4366552017-03-27 14:25:29 -0400386 // umbraColor is the interior value, penumbraColor the exterior value.
387 // umbraAlpha is the factor that is linearly interpolated from outside to inside, and
388 // then "blurred" by the GrBlurredEdgeFP. It is then multiplied by fAmbientAlpha to get
389 // the final alpha.
Jim Van Verth060d9822017-05-04 09:58:17 -0400390 fUmbraColor = SkColorSetARGB(umbraAlpha * 255.9999f, 0, 0, 0);
391 fPenumbraColor = SkColorSetARGB(0, 0, 0, 0);
Jim Van Verthb4366552017-03-27 14:25:29 -0400392
Jim Van Verthda965502017-04-11 15:29:14 -0400393 // make sure we're not below the canvas plane
394 this->setZOffset(path.getBounds(), ctm.hasPerspective());
395
Jim Van Verth7c8008c2018-02-07 15:02:50 -0500396 if (!this->setTransformedHeightFunc(ctm)) {
397 return;
398 }
Jim Van Verthda965502017-04-11 15:29:14 -0400399
Jim Van Verthbce74962017-01-25 09:39:46 -0500400 // Outer ring: 3*numPts
401 // Middle ring: numPts
402 fPositions.setReserve(4 * path.countPoints());
403 fColors.setReserve(4 * path.countPoints());
404 // Outer ring: 12*numPts
405 // Middle ring: 0
406 fIndices.setReserve(12 * path.countPoints());
407
Jim Van Verthbce74962017-01-25 09:39:46 -0500408 // walk around the path, tessellate and generate outer ring
409 // if original path is transparent, will accumulate sum of points for centroid
410 SkPath::Iter iter(path, true);
411 SkPoint pts[4];
412 SkPath::Verb verb;
413 if (fTransparent) {
414 *fPositions.push() = SkPoint::Make(0, 0);
Jim Van Verthb4366552017-03-27 14:25:29 -0400415 *fColors.push() = fUmbraColor;
Jim Van Verthbce74962017-01-25 09:39:46 -0500416 fCentroidCount = 0;
417 }
418 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
419 switch (verb) {
420 case SkPath::kLine_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -0500421 this->INHERITED::handleLine(ctm, &pts[1]);
Jim Van Verthbce74962017-01-25 09:39:46 -0500422 break;
423 case SkPath::kQuad_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -0500424 this->handleQuad(ctm, pts);
Jim Van Verthbce74962017-01-25 09:39:46 -0500425 break;
426 case SkPath::kCubic_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -0500427 this->handleCubic(ctm, pts);
Jim Van Verthbce74962017-01-25 09:39:46 -0500428 break;
429 case SkPath::kConic_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -0500430 this->handleConic(ctm, pts, iter.conicWeight());
Jim Van Verthbce74962017-01-25 09:39:46 -0500431 break;
432 case SkPath::kMove_Verb:
433 case SkPath::kClose_Verb:
434 case SkPath::kDone_Verb:
435 break;
436 }
437 }
438
Brian Salomon0dda9cb2017-02-03 10:33:25 -0500439 if (!this->indexCount()) {
440 return;
441 }
442
Jim Van Verth76387852017-05-16 16:55:16 -0400443 // Finish up
Jim Van Verthbce74962017-01-25 09:39:46 -0500444 SkVector normal;
Jim Van Verthda965502017-04-11 15:29:14 -0400445 if (compute_normal(fPrevPoint, fFirstPoint, fDirection, &normal)) {
446 SkScalar z = fTransformedHeightFunc(fPrevPoint);
447 fRadius = this->offset(z);
448 SkVector scaledNormal(normal);
449 scaledNormal *= fRadius;
450 this->addArc(scaledNormal, true);
Jim Van Verthbce74962017-01-25 09:39:46 -0500451
Jim Van Verth76387852017-05-16 16:55:16 -0400452 // fix-up the last and first umbra points
453 SkVector inset = normal;
454 // adding to an average, so multiply by an additional half
455 inset *= 0.5f*kInsetFactor;
456 fPositions[fPrevUmbraIndex] += inset;
457 fPositions[fFirstVertexIndex] += inset;
458 // we multiply by another half because now we're adding to an average of an average
459 inset *= 0.5f;
460 if (fSplitPreviousEdge) {
461 fPositions[fPrevUmbraIndex - 2] += inset;
462 }
463 if (fSplitFirstEdge) {
464 fPositions[fFirstVertexIndex + 2] += inset;
465 }
466
Jim Van Verthda965502017-04-11 15:29:14 -0400467 // set up for final edge
468 z = fTransformedHeightFunc(fFirstPoint);
469 normal *= this->offset(z);
Jim Van Verthbce74962017-01-25 09:39:46 -0500470
Jim Van Verthda965502017-04-11 15:29:14 -0400471 // make sure we don't end up with a sharp alpha edge along the quad diagonal
Jim Van Verth76387852017-05-16 16:55:16 -0400472 if (fColors[fPrevUmbraIndex] != fColors[fFirstVertexIndex] &&
Cary Clarkdf429f32017-11-08 11:44:31 -0500473 SkPointPriv::DistanceToSqd(fFirstPoint, fPositions[fPrevUmbraIndex]) > kMaxEdgeLenSqr) {
Jim Van Verth76387852017-05-16 16:55:16 -0400474 SkPoint centerPoint = fPositions[fPrevUmbraIndex] + fPositions[fFirstVertexIndex];
Jim Van Verthda965502017-04-11 15:29:14 -0400475 centerPoint *= 0.5f;
476 *fPositions.push() = centerPoint;
Jim Van Verth76387852017-05-16 16:55:16 -0400477 *fColors.push() = SkPMLerp(fColors[fFirstVertexIndex], fColors[fPrevUmbraIndex], 128);
478 centerPoint = fPositions[fPositions.count()-2] + fPositions[fFirstVertexIndex+1];
479 centerPoint *= 0.5f;
480 *fPositions.push() = centerPoint;
Jim Van Verthda965502017-04-11 15:29:14 -0400481 *fColors.push() = fPenumbraColor;
482
Jim Van Verth76387852017-05-16 16:55:16 -0400483 if (fColors[fPrevUmbraIndex] > fColors[fPositions.count() - 2]) {
484 *fIndices.push() = fPrevUmbraIndex;
485 *fIndices.push() = fPositions.count() - 3;
486 *fIndices.push() = fPositions.count() - 2;
Jim Van Verthda965502017-04-11 15:29:14 -0400487
Jim Van Verth76387852017-05-16 16:55:16 -0400488 *fIndices.push() = fPositions.count() - 3;
489 *fIndices.push() = fPositions.count() - 1;
490 *fIndices.push() = fPositions.count() - 2;
491 } else {
492 *fIndices.push() = fPrevUmbraIndex;
493 *fIndices.push() = fPositions.count() - 2;
494 *fIndices.push() = fPositions.count() - 1;
495
496 *fIndices.push() = fPrevUmbraIndex;
497 *fIndices.push() = fPositions.count() - 1;
498 *fIndices.push() = fPositions.count() - 3;
499 }
Jim Van Verthda965502017-04-11 15:29:14 -0400500
Jim Van Verth1c4c1142017-05-11 10:23:53 -0400501 // if transparent, add point to first one in array and add to center fan
502 if (fTransparent) {
503 fPositions[0] += centerPoint;
504 ++fCentroidCount;
505
506 *fIndices.push() = 0;
507 *fIndices.push() = fPrevUmbraIndex;
508 *fIndices.push() = fPositions.count() - 2;
509 }
510
Jim Van Verthda965502017-04-11 15:29:14 -0400511 fPrevUmbraIndex = fPositions.count() - 2;
512 }
513
514 // final edge
Jim Van Vertha84898d2017-02-06 13:38:23 -0500515 *fPositions.push() = fFirstPoint + normal;
Jim Van Verthbce74962017-01-25 09:39:46 -0500516 *fColors.push() = fPenumbraColor;
517
Jim Van Verth76387852017-05-16 16:55:16 -0400518 if (fColors[fPrevUmbraIndex] > fColors[fFirstVertexIndex]) {
Jim Van Verthda965502017-04-11 15:29:14 -0400519 *fIndices.push() = fPrevUmbraIndex;
520 *fIndices.push() = fPositions.count() - 2;
Jim Van Verth76387852017-05-16 16:55:16 -0400521 *fIndices.push() = fFirstVertexIndex;
Jim Van Verthbce74962017-01-25 09:39:46 -0500522
Jim Van Verthda965502017-04-11 15:29:14 -0400523 *fIndices.push() = fPositions.count() - 2;
524 *fIndices.push() = fPositions.count() - 1;
Jim Van Verth76387852017-05-16 16:55:16 -0400525 *fIndices.push() = fFirstVertexIndex;
Jim Van Verthda965502017-04-11 15:29:14 -0400526 } else {
527 *fIndices.push() = fPrevUmbraIndex;
528 *fIndices.push() = fPositions.count() - 2;
529 *fIndices.push() = fPositions.count() - 1;
530
531 *fIndices.push() = fPrevUmbraIndex;
532 *fIndices.push() = fPositions.count() - 1;
Jim Van Verth76387852017-05-16 16:55:16 -0400533 *fIndices.push() = fFirstVertexIndex;
Jim Van Verthda965502017-04-11 15:29:14 -0400534 }
Jim Van Verth76387852017-05-16 16:55:16 -0400535 fPrevOutset = normal;
Jim Van Verthbce74962017-01-25 09:39:46 -0500536 }
537
538 // finalize centroid
539 if (fTransparent) {
540 fPositions[0] *= SkScalarFastInvert(fCentroidCount);
Jim Van Verth1c4c1142017-05-11 10:23:53 -0400541 fColors[0] = this->umbraColor(fTransformedHeightFunc(fPositions[0]));
Jim Van Verthbce74962017-01-25 09:39:46 -0500542
543 *fIndices.push() = 0;
Brian Salomon66085ed2017-02-02 15:39:34 -0500544 *fIndices.push() = fPrevUmbraIndex;
Jim Van Verth76387852017-05-16 16:55:16 -0400545 *fIndices.push() = fFirstVertexIndex;
Jim Van Verthbce74962017-01-25 09:39:46 -0500546 }
547
548 // final fan
549 if (fPositions.count() >= 3) {
Jim Van Verth76387852017-05-16 16:55:16 -0400550 fPrevUmbraIndex = fFirstVertexIndex;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500551 fPrevPoint = fFirstPoint;
Jim Van Verthda965502017-04-11 15:29:14 -0400552 fRadius = this->offset(fTransformedHeightFunc(fPrevPoint));
Jim Van Verth76387852017-05-16 16:55:16 -0400553 if (this->addArc(fFirstOutset, false)) {
554 *fIndices.push() = fFirstVertexIndex;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400555 *fIndices.push() = fPositions.count() - 1;
Jim Van Verth76387852017-05-16 16:55:16 -0400556 *fIndices.push() = fFirstVertexIndex + 1;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400557 } else {
558 // arc is too small, set the first penumbra point to be the same position
559 // as the last one
Jim Van Verth76387852017-05-16 16:55:16 -0400560 fPositions[fFirstVertexIndex + 1] = fPositions[fPositions.count() - 1];
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400561 }
Jim Van Verthbce74962017-01-25 09:39:46 -0500562 }
Brian Salomon0dda9cb2017-02-03 10:33:25 -0500563 fSucceeded = true;
Jim Van Verthbce74962017-01-25 09:39:46 -0500564}
565
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500566void SkAmbientShadowTessellator::handleLine(const SkPoint& p) {
Jim Van Verthbce74962017-01-25 09:39:46 -0500567 if (fInitPoints.count() < 2) {
568 *fInitPoints.push() = p;
569 return;
570 }
571
572 if (fInitPoints.count() == 2) {
573 // determine if cw or ccw
574 SkVector v0 = fInitPoints[1] - fInitPoints[0];
575 SkVector v1 = p - fInitPoints[0];
576 SkScalar perpDot = v0.fX*v1.fY - v0.fY*v1.fX;
577 if (SkScalarNearlyZero(perpDot)) {
578 // nearly parallel, just treat as straight line and continue
579 fInitPoints[1] = p;
580 return;
581 }
582
583 // if perpDot > 0, winding is ccw
584 fDirection = (perpDot > 0) ? -1 : 1;
585
586 // add first quad
Jim Van Verthda965502017-04-11 15:29:14 -0400587 SkVector normal;
588 if (!compute_normal(fInitPoints[0], fInitPoints[1], fDirection, &normal)) {
Jim Van Verthbce74962017-01-25 09:39:46 -0500589 // first two points are incident, make the third point the second and continue
590 fInitPoints[1] = p;
591 return;
592 }
593
Jim Van Vertha84898d2017-02-06 13:38:23 -0500594 fFirstPoint = fInitPoints[0];
Jim Van Verth76387852017-05-16 16:55:16 -0400595 fFirstVertexIndex = fPositions.count();
Jim Van Verthda965502017-04-11 15:29:14 -0400596 SkScalar z = fTransformedHeightFunc(fFirstPoint);
Jim Van Verth76387852017-05-16 16:55:16 -0400597 fFirstOutset = normal;
598 fFirstOutset *= this->offset(z);
Jim Van Verthda965502017-04-11 15:29:14 -0400599
Jim Van Verth76387852017-05-16 16:55:16 -0400600 fPrevOutset = fFirstOutset;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500601 fPrevPoint = fFirstPoint;
Jim Van Verth76387852017-05-16 16:55:16 -0400602 fPrevUmbraIndex = fFirstVertexIndex;
Jim Van Verthbce74962017-01-25 09:39:46 -0500603
Jim Van Verthda965502017-04-11 15:29:14 -0400604 *fPositions.push() = fFirstPoint;
605 *fColors.push() = this->umbraColor(z);
Jim Van Verth76387852017-05-16 16:55:16 -0400606 *fPositions.push() = fFirstPoint + fFirstOutset;
Jim Van Verthbce74962017-01-25 09:39:46 -0500607 *fColors.push() = fPenumbraColor;
608 if (fTransparent) {
Jim Van Verthda965502017-04-11 15:29:14 -0400609 fPositions[0] += fFirstPoint;
Jim Van Verthbce74962017-01-25 09:39:46 -0500610 fCentroidCount = 1;
611 }
Jim Van Verthda965502017-04-11 15:29:14 -0400612
613 // add the first quad
614 z = fTransformedHeightFunc(fInitPoints[1]);
615 fRadius = this->offset(z);
616 fUmbraColor = this->umbraColor(z);
Jim Van Verthda965502017-04-11 15:29:14 -0400617 this->addEdge(fInitPoints[1], normal);
Jim Van Verthbce74962017-01-25 09:39:46 -0500618
619 // to ensure we skip this block next time
620 *fInitPoints.push() = p;
621 }
622
623 SkVector normal;
Jim Van Verth76387852017-05-16 16:55:16 -0400624 if (compute_normal(fPrevPoint, p, fDirection, &normal)) {
Jim Van Verthda965502017-04-11 15:29:14 -0400625 SkVector scaledNormal = normal;
626 scaledNormal *= fRadius;
627 this->addArc(scaledNormal, true);
628 SkScalar z = fTransformedHeightFunc(p);
629 fRadius = this->offset(z);
630 fUmbraColor = this->umbraColor(z);
Jim Van Verthda965502017-04-11 15:29:14 -0400631 this->addEdge(p, normal);
Jim Van Verthbce74962017-01-25 09:39:46 -0500632 }
633}
634
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500635void SkAmbientShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVector& nextNormal) {
Jim Van Verth76387852017-05-16 16:55:16 -0400636 // We compute the inset in two stages: first we inset by half the current normal,
637 // then on the next addEdge() we add half of the next normal to get an average of the two
638 SkVector insetNormal = nextNormal;
639 insetNormal *= 0.5f*kInsetFactor;
640
641 // Adding the other half of the average for the previous edge
642 fPositions[fPrevUmbraIndex] += insetNormal;
643
644 SkPoint umbraPoint = nextPoint + insetNormal;
645 SkVector outsetNormal = nextNormal;
646 outsetNormal *= fRadius;
647 SkPoint penumbraPoint = nextPoint + outsetNormal;
648
649 // For split edges, we're adding an average of two averages, so we multiply by another half
650 if (fSplitPreviousEdge) {
651 insetNormal *= 0.5f;
652 fPositions[fPrevUmbraIndex - 2] += insetNormal;
653 }
654
655 // Split the edge to make sure we don't end up with a sharp alpha edge along the quad diagonal
Jim Van Verthda965502017-04-11 15:29:14 -0400656 if (fColors[fPrevUmbraIndex] != fUmbraColor &&
Cary Clarkdf429f32017-11-08 11:44:31 -0500657 SkPointPriv::DistanceToSqd(nextPoint, fPositions[fPrevUmbraIndex]) > kMaxEdgeLenSqr) {
Jim Van Verth76387852017-05-16 16:55:16 -0400658
659 // This is lacking 1/4 of the next inset -- we'll add it the next time we call addEdge()
660 SkPoint centerPoint = fPositions[fPrevUmbraIndex] + umbraPoint;
Jim Van Verthda965502017-04-11 15:29:14 -0400661 centerPoint *= 0.5f;
662 *fPositions.push() = centerPoint;
663 *fColors.push() = SkPMLerp(fUmbraColor, fColors[fPrevUmbraIndex], 128);
Jim Van Verth76387852017-05-16 16:55:16 -0400664 centerPoint = fPositions[fPositions.count()-2] + penumbraPoint;
665 centerPoint *= 0.5f;
666 *fPositions.push() = centerPoint;
Jim Van Verthda965502017-04-11 15:29:14 -0400667 *fColors.push() = fPenumbraColor;
668
669 // set triangularization to get best interpolation of color
670 if (fColors[fPrevUmbraIndex] > fColors[fPositions.count() - 2]) {
671 *fIndices.push() = fPrevUmbraIndex;
672 *fIndices.push() = fPositions.count() - 3;
673 *fIndices.push() = fPositions.count() - 2;
674
675 *fIndices.push() = fPositions.count() - 3;
676 *fIndices.push() = fPositions.count() - 1;
677 *fIndices.push() = fPositions.count() - 2;
678 } else {
679 *fIndices.push() = fPrevUmbraIndex;
680 *fIndices.push() = fPositions.count() - 2;
681 *fIndices.push() = fPositions.count() - 1;
682
683 *fIndices.push() = fPrevUmbraIndex;
684 *fIndices.push() = fPositions.count() - 1;
685 *fIndices.push() = fPositions.count() - 3;
686 }
687
Jim Van Verth1c4c1142017-05-11 10:23:53 -0400688 // if transparent, add point to first one in array and add to center fan
689 if (fTransparent) {
690 fPositions[0] += centerPoint;
691 ++fCentroidCount;
692
693 *fIndices.push() = 0;
694 *fIndices.push() = fPrevUmbraIndex;
695 *fIndices.push() = fPositions.count() - 2;
696 }
697
Jim Van Verth76387852017-05-16 16:55:16 -0400698 fSplitPreviousEdge = true;
699 if (fPrevUmbraIndex == fFirstVertexIndex) {
700 fSplitFirstEdge = true;
701 }
Jim Van Verthda965502017-04-11 15:29:14 -0400702 fPrevUmbraIndex = fPositions.count() - 2;
Jim Van Verth76387852017-05-16 16:55:16 -0400703 } else {
704 fSplitPreviousEdge = false;
Jim Van Verthda965502017-04-11 15:29:14 -0400705 }
706
Jim Van Verthbce74962017-01-25 09:39:46 -0500707 // add next quad
Jim Van Verth76387852017-05-16 16:55:16 -0400708 *fPositions.push() = umbraPoint;
Jim Van Verthbce74962017-01-25 09:39:46 -0500709 *fColors.push() = fUmbraColor;
Jim Van Verth76387852017-05-16 16:55:16 -0400710 *fPositions.push() = penumbraPoint;
Jim Van Verthbce74962017-01-25 09:39:46 -0500711 *fColors.push() = fPenumbraColor;
712
Jim Van Verthda965502017-04-11 15:29:14 -0400713 // set triangularization to get best interpolation of color
714 if (fColors[fPrevUmbraIndex] > fColors[fPositions.count() - 2]) {
715 *fIndices.push() = fPrevUmbraIndex;
716 *fIndices.push() = fPositions.count() - 3;
717 *fIndices.push() = fPositions.count() - 2;
Jim Van Verthbce74962017-01-25 09:39:46 -0500718
Jim Van Verthda965502017-04-11 15:29:14 -0400719 *fIndices.push() = fPositions.count() - 3;
720 *fIndices.push() = fPositions.count() - 1;
721 *fIndices.push() = fPositions.count() - 2;
722 } else {
723 *fIndices.push() = fPrevUmbraIndex;
724 *fIndices.push() = fPositions.count() - 2;
725 *fIndices.push() = fPositions.count() - 1;
726
727 *fIndices.push() = fPrevUmbraIndex;
728 *fIndices.push() = fPositions.count() - 1;
729 *fIndices.push() = fPositions.count() - 3;
730 }
Jim Van Verthbce74962017-01-25 09:39:46 -0500731
732 // if transparent, add point to first one in array and add to center fan
733 if (fTransparent) {
734 fPositions[0] += nextPoint;
735 ++fCentroidCount;
736
737 *fIndices.push() = 0;
Brian Salomon66085ed2017-02-02 15:39:34 -0500738 *fIndices.push() = fPrevUmbraIndex;
Jim Van Verthbce74962017-01-25 09:39:46 -0500739 *fIndices.push() = fPositions.count() - 2;
740 }
741
Brian Salomon66085ed2017-02-02 15:39:34 -0500742 fPrevUmbraIndex = fPositions.count() - 2;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500743 fPrevPoint = nextPoint;
Jim Van Verth76387852017-05-16 16:55:16 -0400744 fPrevOutset = outsetNormal;
Jim Van Verthbce74962017-01-25 09:39:46 -0500745}
Jim Van Verth91af7272017-01-27 14:15:54 -0500746
747///////////////////////////////////////////////////////////////////////////////////////////////////
748
Brian Salomonaff27a22017-02-06 15:47:44 -0500749class SkSpotShadowTessellator : public SkBaseShadowTessellator {
Brian Salomon958fbc42017-01-30 17:01:28 -0500750public:
Jim Van Vertha84898d2017-02-06 13:38:23 -0500751 SkSpotShadowTessellator(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -0400752 const SkPoint3& zPlaneParams, const SkPoint3& lightPos,
Jim Van Verth060d9822017-05-04 09:58:17 -0400753 SkScalar lightRadius, bool transparent);
Brian Salomon958fbc42017-01-30 17:01:28 -0500754
Brian Salomon958fbc42017-01-30 17:01:28 -0500755private:
Brian Salomonab664fa2017-03-24 16:07:20 +0000756 void computeClipAndPathPolygons(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthda965502017-04-11 15:29:14 -0400757 const SkMatrix& shadowTransform);
Brian Salomonab664fa2017-03-24 16:07:20 +0000758 void computeClipVectorsAndTestCentroid();
Brian Salomon66085ed2017-02-02 15:39:34 -0500759 bool clipUmbraPoint(const SkPoint& umbraPoint, const SkPoint& centroid, SkPoint* clipPoint);
Brian Salomonab664fa2017-03-24 16:07:20 +0000760 int getClosestUmbraPoint(const SkPoint& point);
Brian Salomon958fbc42017-01-30 17:01:28 -0500761
Jim Van Vertha84898d2017-02-06 13:38:23 -0500762 void handleLine(const SkPoint& p) override;
Jim Van Verthb55eb282017-07-18 14:13:45 -0400763 bool handlePolyPoint(const SkPoint& p);
Brian Salomon958fbc42017-01-30 17:01:28 -0500764
765 void mapPoints(SkScalar scale, const SkVector& xlate, SkPoint* pts, int count);
Brian Salomonab664fa2017-03-24 16:07:20 +0000766 bool addInnerPoint(const SkPoint& pathPoint);
Jim Van Verthda965502017-04-11 15:29:14 -0400767 void addEdge(const SkVector& nextPoint, const SkVector& nextNormal);
768
769 SkScalar offset(SkScalar z) {
770 float zRatio = SkTPin(z / (fLightZ - z), 0.0f, 0.95f);
771 return fLightRadius*zRatio;
772 }
773
774 SkScalar fLightZ;
775 SkScalar fLightRadius;
776 SkScalar fOffsetAdjust;
Brian Salomon958fbc42017-01-30 17:01:28 -0500777
Brian Salomon958fbc42017-01-30 17:01:28 -0500778 SkTDArray<SkPoint> fClipPolygon;
Brian Salomon66085ed2017-02-02 15:39:34 -0500779 SkTDArray<SkVector> fClipVectors;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500780 SkPoint fCentroid;
Brian Salomonab664fa2017-03-24 16:07:20 +0000781 SkScalar fArea;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500782
Brian Salomonab664fa2017-03-24 16:07:20 +0000783 SkTDArray<SkPoint> fPathPolygon;
784 SkTDArray<SkPoint> fUmbraPolygon;
785 int fCurrClipPoint;
786 int fCurrUmbraPoint;
Brian Salomon66085ed2017-02-02 15:39:34 -0500787 bool fPrevUmbraOutside;
788 bool fFirstUmbraOutside;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500789 bool fValidUmbra;
Brian Salomon958fbc42017-01-30 17:01:28 -0500790
Brian Salomonaff27a22017-02-06 15:47:44 -0500791 typedef SkBaseShadowTessellator INHERITED;
Brian Salomon958fbc42017-01-30 17:01:28 -0500792};
793
Jim Van Vertha84898d2017-02-06 13:38:23 -0500794SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -0400795 const SkPoint3& zPlaneParams,
Jim Van Verthb4366552017-03-27 14:25:29 -0400796 const SkPoint3& lightPos, SkScalar lightRadius,
Jim Van Verth060d9822017-05-04 09:58:17 -0400797 bool transparent)
Jim Van Verthe308a122017-05-08 14:19:30 -0400798 : INHERITED(zPlaneParams, transparent)
Jim Van Verthda965502017-04-11 15:29:14 -0400799 , fLightZ(lightPos.fZ)
800 , fLightRadius(lightRadius)
801 , fOffsetAdjust(0)
802 , fCurrClipPoint(0)
803 , fPrevUmbraOutside(false)
804 , fFirstUmbraOutside(false)
805 , fValidUmbra(true) {
806
Jim Van Verth22526362018-02-28 14:51:19 -0500807 // TODO: support some concave paths
Mike Reed9d5c6742018-03-06 10:31:27 -0500808 SkASSERT(path.isConvex());
Jim Van Verth22526362018-02-28 14:51:19 -0500809
Jim Van Verthda965502017-04-11 15:29:14 -0400810 // make sure we're not below the canvas plane
811 if (this->setZOffset(path.getBounds(), ctm.hasPerspective())) {
812 // Adjust light height and radius
813 fLightRadius *= (fLightZ + fZOffset) / fLightZ;
814 fLightZ += fZOffset;
815 }
Jim Van Verthb4366552017-03-27 14:25:29 -0400816
817 // Set radius and colors
Jim Van Verthb4366552017-03-27 14:25:29 -0400818 SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY());
Jim Van Verthe308a122017-05-08 14:19:30 -0400819 SkScalar occluderHeight = this->heightFunc(center.fX, center.fY) + fZOffset;
Jim Van Verth060d9822017-05-04 09:58:17 -0400820 fUmbraColor = SkColorSetARGB(255, 0, 0, 0);
821 fPenumbraColor = SkColorSetARGB(0, 0, 0, 0);
Jim Van Verthb4366552017-03-27 14:25:29 -0400822
Jim Van Verth1af03d42017-07-31 09:34:58 -0400823 // Compute the blur radius, scale and translation for the spot shadow.
824 SkScalar radius;
Jim Van Verthda965502017-04-11 15:29:14 -0400825 SkMatrix shadowTransform;
826 if (!ctm.hasPerspective()) {
Jim Van Verth1af03d42017-07-31 09:34:58 -0400827 SkScalar scale;
828 SkVector translate;
829 SkDrawShadowMetrics::GetSpotParams(occluderHeight, lightPos.fX, lightPos.fY, fLightZ,
830 lightRadius, &radius, &scale, &translate);
Jim Van Verthda965502017-04-11 15:29:14 -0400831 shadowTransform.setScaleTranslate(scale, scale, translate.fX, translate.fY);
832 } else {
833 // For perspective, we have a scale, a z-shear, and another projective divide --
834 // this varies at each point so we can't use an affine transform.
835 // We'll just apply this to each generated point in turn.
836 shadowTransform.reset();
837 // Also can't cull the center (for now).
838 fTransparent = true;
Jim Van Verth1af03d42017-07-31 09:34:58 -0400839 radius = SkDrawShadowMetrics::SpotBlurRadius(occluderHeight, lightPos.fZ, lightRadius);
Jim Van Verthda965502017-04-11 15:29:14 -0400840 }
Jim Van Verth1af03d42017-07-31 09:34:58 -0400841 fRadius = radius;
Jim Van Verthda965502017-04-11 15:29:14 -0400842 SkMatrix fullTransform = SkMatrix::Concat(shadowTransform, ctm);
843
844 // Set up our reverse mapping
Jim Van Verth7c8008c2018-02-07 15:02:50 -0500845 if (!this->setTransformedHeightFunc(fullTransform)) {
846 return;
847 }
Jim Van Verthb4366552017-03-27 14:25:29 -0400848
Brian Salomonab664fa2017-03-24 16:07:20 +0000849 // TODO: calculate these reserves better
Brian Salomon66085ed2017-02-02 15:39:34 -0500850 // Penumbra ring: 3*numPts
851 // Umbra ring: numPts
Jim Van Verth91af7272017-01-27 14:15:54 -0500852 // Inner ring: numPts
Brian Salomon66085ed2017-02-02 15:39:34 -0500853 fPositions.setReserve(5 * path.countPoints());
854 fColors.setReserve(5 * path.countPoints());
855 // Penumbra ring: 12*numPts
856 // Umbra ring: 3*numPts
857 fIndices.setReserve(15 * path.countPoints());
Brian Salomone7c85c42017-03-24 16:00:35 +0000858 fClipPolygon.setReserve(path.countPoints());
Brian Salomonab664fa2017-03-24 16:07:20 +0000859
860 // compute rough clip bounds for umbra, plus offset polygon, plus centroid
Jim Van Verthda965502017-04-11 15:29:14 -0400861 this->computeClipAndPathPolygons(path, ctm, shadowTransform);
Brian Salomonab664fa2017-03-24 16:07:20 +0000862 if (fClipPolygon.count() < 3 || fPathPolygon.count() < 3) {
Brian Salomon66085ed2017-02-02 15:39:34 -0500863 return;
864 }
Brian Salomon66085ed2017-02-02 15:39:34 -0500865
Brian Salomonab664fa2017-03-24 16:07:20 +0000866 // check to see if umbra collapses
Cary Clarkdf429f32017-11-08 11:44:31 -0500867 SkScalar minDistSq = SkPointPriv::DistanceToLineSegmentBetweenSqd(fCentroid, fPathPolygon[0],
868 fPathPolygon[1]);
Jim Van Verthda965502017-04-11 15:29:14 -0400869 SkRect bounds;
870 bounds.setBounds(&fPathPolygon[0], fPathPolygon.count());
Brian Salomonab664fa2017-03-24 16:07:20 +0000871 for (int i = 1; i < fPathPolygon.count(); ++i) {
872 int j = i + 1;
873 if (i == fPathPolygon.count() - 1) {
874 j = 0;
875 }
876 SkPoint currPoint = fPathPolygon[i];
877 SkPoint nextPoint = fPathPolygon[j];
Cary Clarkdf429f32017-11-08 11:44:31 -0500878 SkScalar distSq = SkPointPriv::DistanceToLineSegmentBetweenSqd(fCentroid, currPoint,
879 nextPoint);
Brian Salomonab664fa2017-03-24 16:07:20 +0000880 if (distSq < minDistSq) {
881 minDistSq = distSq;
882 }
883 }
884 static constexpr auto kTolerance = 1.0e-2f;
885 if (minDistSq < (radius + kTolerance)*(radius + kTolerance)) {
886 // if the umbra would collapse, we back off a bit on inner blur and adjust the alpha
887 SkScalar newRadius = SkScalarSqrt(minDistSq) - kTolerance;
Jim Van Verthda965502017-04-11 15:29:14 -0400888 fOffsetAdjust = newRadius - radius;
Jim Van Verthb6069df2017-04-28 11:00:35 -0400889 SkScalar ratio = 128 * (newRadius + radius) / radius;
Brian Salomonab664fa2017-03-24 16:07:20 +0000890 // they aren't PMColors, but the interpolation algorithm is the same
891 fUmbraColor = SkPMLerp(fUmbraColor, fPenumbraColor, (unsigned)ratio);
892 radius = newRadius;
893 }
Jim Van Verth91af7272017-01-27 14:15:54 -0500894
Brian Salomonab664fa2017-03-24 16:07:20 +0000895 // compute vectors for clip tests
896 this->computeClipVectorsAndTestCentroid();
897
898 // generate inner ring
Jim Van Verthda965502017-04-11 15:29:14 -0400899 if (!SkInsetConvexPolygon(&fPathPolygon[0], fPathPolygon.count(), radius,
900 &fUmbraPolygon)) {
Brian Salomonab664fa2017-03-24 16:07:20 +0000901 // this shouldn't happen, but just in case we'll inset using the centroid
902 fValidUmbra = false;
903 }
904
905 // walk around the path polygon, generate outer ring and connect to inner ring
Brian Salomon66085ed2017-02-02 15:39:34 -0500906 if (fTransparent) {
907 *fPositions.push() = fCentroid;
908 *fColors.push() = fUmbraColor;
909 }
Brian Salomonab664fa2017-03-24 16:07:20 +0000910 fCurrUmbraPoint = 0;
911 for (int i = 0; i < fPathPolygon.count(); ++i) {
Jim Van Verthb55eb282017-07-18 14:13:45 -0400912 if (!this->handlePolyPoint(fPathPolygon[i])) {
913 return;
914 }
Jim Van Verth91af7272017-01-27 14:15:54 -0500915 }
916
Brian Salomon0dda9cb2017-02-03 10:33:25 -0500917 if (!this->indexCount()) {
918 return;
919 }
920
Brian Salomonab664fa2017-03-24 16:07:20 +0000921 // finish up the final verts
Jim Van Verth91af7272017-01-27 14:15:54 -0500922 SkVector normal;
Jim Van Verthda965502017-04-11 15:29:14 -0400923 if (compute_normal(fPrevPoint, fFirstPoint, fDirection, &normal)) {
924 normal *= fRadius;
925 this->addArc(normal, true);
Jim Van Verth91af7272017-01-27 14:15:54 -0500926
Brian Salomon66085ed2017-02-02 15:39:34 -0500927 // add to center fan
928 if (fTransparent) {
929 *fIndices.push() = 0;
930 *fIndices.push() = fPrevUmbraIndex;
Jim Van Verth76387852017-05-16 16:55:16 -0400931 *fIndices.push() = fFirstVertexIndex;
Brian Salomon66085ed2017-02-02 15:39:34 -0500932 // or to clip ring
933 } else {
934 if (fFirstUmbraOutside) {
935 *fIndices.push() = fPrevUmbraIndex;
Jim Van Verth76387852017-05-16 16:55:16 -0400936 *fIndices.push() = fFirstVertexIndex;
937 *fIndices.push() = fFirstVertexIndex + 1;
Brian Salomon66085ed2017-02-02 15:39:34 -0500938 if (fPrevUmbraOutside) {
939 // fill out quad
940 *fIndices.push() = fPrevUmbraIndex;
Jim Van Verth76387852017-05-16 16:55:16 -0400941 *fIndices.push() = fFirstVertexIndex + 1;
Brian Salomon66085ed2017-02-02 15:39:34 -0500942 *fIndices.push() = fPrevUmbraIndex + 1;
943 }
944 } else if (fPrevUmbraOutside) {
945 // add tri
946 *fIndices.push() = fPrevUmbraIndex;
Jim Van Verth76387852017-05-16 16:55:16 -0400947 *fIndices.push() = fFirstVertexIndex;
Brian Salomon66085ed2017-02-02 15:39:34 -0500948 *fIndices.push() = fPrevUmbraIndex + 1;
949 }
950 }
951
Jim Van Verth91af7272017-01-27 14:15:54 -0500952 // add final edge
953 *fPositions.push() = fFirstPoint + normal;
954 *fColors.push() = fPenumbraColor;
955
Brian Salomon66085ed2017-02-02 15:39:34 -0500956 *fIndices.push() = fPrevUmbraIndex;
Jim Van Verth91af7272017-01-27 14:15:54 -0500957 *fIndices.push() = fPositions.count() - 2;
Jim Van Verth76387852017-05-16 16:55:16 -0400958 *fIndices.push() = fFirstVertexIndex;
Jim Van Verth91af7272017-01-27 14:15:54 -0500959
960 *fIndices.push() = fPositions.count() - 2;
961 *fIndices.push() = fPositions.count() - 1;
Jim Van Verth76387852017-05-16 16:55:16 -0400962 *fIndices.push() = fFirstVertexIndex;
Jim Van Verthda965502017-04-11 15:29:14 -0400963
Jim Van Verth76387852017-05-16 16:55:16 -0400964 fPrevOutset = normal;
Jim Van Verth91af7272017-01-27 14:15:54 -0500965 }
966
967 // final fan
968 if (fPositions.count() >= 3) {
Jim Van Verth76387852017-05-16 16:55:16 -0400969 fPrevUmbraIndex = fFirstVertexIndex;
Jim Van Verth91af7272017-01-27 14:15:54 -0500970 fPrevPoint = fFirstPoint;
Jim Van Verth76387852017-05-16 16:55:16 -0400971 if (this->addArc(fFirstOutset, false)) {
972 *fIndices.push() = fFirstVertexIndex;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400973 *fIndices.push() = fPositions.count() - 1;
974 if (fFirstUmbraOutside) {
Jim Van Verth76387852017-05-16 16:55:16 -0400975 *fIndices.push() = fFirstVertexIndex + 2;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400976 } else {
Jim Van Verth76387852017-05-16 16:55:16 -0400977 *fIndices.push() = fFirstVertexIndex + 1;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400978 }
Brian Salomon66085ed2017-02-02 15:39:34 -0500979 } else {
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400980 // no arc added, fix up by setting first penumbra point position to last one
981 if (fFirstUmbraOutside) {
Jim Van Verth76387852017-05-16 16:55:16 -0400982 fPositions[fFirstVertexIndex + 2] = fPositions[fPositions.count() - 1];
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400983 } else {
Jim Van Verth76387852017-05-16 16:55:16 -0400984 fPositions[fFirstVertexIndex + 1] = fPositions[fPositions.count() - 1];
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400985 }
Brian Salomon66085ed2017-02-02 15:39:34 -0500986 }
Jim Van Verth91af7272017-01-27 14:15:54 -0500987 }
Jim Van Verthda965502017-04-11 15:29:14 -0400988
989 if (ctm.hasPerspective()) {
990 for (int i = 0; i < fPositions.count(); ++i) {
991 SkScalar pathZ = fTransformedHeightFunc(fPositions[i]);
992 SkScalar factor = SkScalarInvert(fLightZ - pathZ);
993 fPositions[i].fX = (fPositions[i].fX*fLightZ - lightPos.fX*pathZ)*factor;
994 fPositions[i].fY = (fPositions[i].fY*fLightZ - lightPos.fY*pathZ)*factor;
995 }
996#ifdef DRAW_CENTROID
997 SkScalar pathZ = fTransformedHeightFunc(fCentroid);
998 SkScalar factor = SkScalarInvert(fLightZ - pathZ);
999 fCentroid.fX = (fCentroid.fX*fLightZ - lightPos.fX*pathZ)*factor;
1000 fCentroid.fY = (fCentroid.fY*fLightZ - lightPos.fY*pathZ)*factor;
1001#endif
1002 }
1003#ifdef DRAW_CENTROID
1004 *fPositions.push() = fCentroid + SkVector::Make(-2, -2);
1005 *fColors.push() = SkColorSetARGB(255, 0, 255, 255);
1006 *fPositions.push() = fCentroid + SkVector::Make(2, -2);
1007 *fColors.push() = SkColorSetARGB(255, 0, 255, 255);
1008 *fPositions.push() = fCentroid + SkVector::Make(-2, 2);
1009 *fColors.push() = SkColorSetARGB(255, 0, 255, 255);
1010 *fPositions.push() = fCentroid + SkVector::Make(2, 2);
1011 *fColors.push() = SkColorSetARGB(255, 0, 255, 255);
1012
1013 *fIndices.push() = fPositions.count() - 4;
1014 *fIndices.push() = fPositions.count() - 2;
1015 *fIndices.push() = fPositions.count() - 1;
1016
1017 *fIndices.push() = fPositions.count() - 4;
1018 *fIndices.push() = fPositions.count() - 1;
1019 *fIndices.push() = fPositions.count() - 3;
1020#endif
1021
Brian Salomon0dda9cb2017-02-03 10:33:25 -05001022 fSucceeded = true;
Jim Van Verth91af7272017-01-27 14:15:54 -05001023}
1024
Brian Salomonab664fa2017-03-24 16:07:20 +00001025void SkSpotShadowTessellator::computeClipAndPathPolygons(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthda965502017-04-11 15:29:14 -04001026 const SkMatrix& shadowTransform) {
Brian Salomonab664fa2017-03-24 16:07:20 +00001027
1028 fPathPolygon.setReserve(path.countPoints());
1029
1030 // Walk around the path and compute clip polygon and path polygon.
1031 // Will also accumulate sum of areas for centroid.
1032 // For Bezier curves, we compute additional interior points on curve.
Jim Van Verth91af7272017-01-27 14:15:54 -05001033 SkPath::Iter iter(path, true);
1034 SkPoint pts[4];
1035 SkPath::Verb verb;
1036
Jim Van Verth91af7272017-01-27 14:15:54 -05001037 fClipPolygon.reset();
1038
Brian Salomonab664fa2017-03-24 16:07:20 +00001039 // init centroid
1040 fCentroid = SkPoint::Make(0, 0);
1041 fArea = 0;
1042
Brian Salomon66085ed2017-02-02 15:39:34 -05001043 // coefficients to compute cubic Bezier at t = 5/16
Brian Salomonab664fa2017-03-24 16:07:20 +00001044 static constexpr SkScalar kA = 0.32495117187f;
1045 static constexpr SkScalar kB = 0.44311523437f;
1046 static constexpr SkScalar kC = 0.20141601562f;
1047 static constexpr SkScalar kD = 0.03051757812f;
Brian Salomon66085ed2017-02-02 15:39:34 -05001048
1049 SkPoint curvePoint;
1050 SkScalar w;
Jim Van Verth91af7272017-01-27 14:15:54 -05001051 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1052 switch (verb) {
Jim Van Verth91af7272017-01-27 14:15:54 -05001053 case SkPath::kLine_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -05001054 ctm.mapPoints(&pts[1], 1);
Jim Van Verth91af7272017-01-27 14:15:54 -05001055 *fClipPolygon.push() = pts[1];
Brian Salomonab664fa2017-03-24 16:07:20 +00001056 this->INHERITED::handleLine(shadowTransform, &pts[1]);
Jim Van Verth91af7272017-01-27 14:15:54 -05001057 break;
1058 case SkPath::kQuad_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -05001059 ctm.mapPoints(pts, 3);
Brian Salomon66085ed2017-02-02 15:39:34 -05001060 // point at t = 1/2
1061 curvePoint.fX = 0.25f*pts[0].fX + 0.5f*pts[1].fX + 0.25f*pts[2].fX;
1062 curvePoint.fY = 0.25f*pts[0].fY + 0.5f*pts[1].fY + 0.25f*pts[2].fY;
1063 *fClipPolygon.push() = curvePoint;
Brian Salomon66085ed2017-02-02 15:39:34 -05001064 *fClipPolygon.push() = pts[2];
Brian Salomonab664fa2017-03-24 16:07:20 +00001065 this->handleQuad(shadowTransform, pts);
Jim Van Verth91af7272017-01-27 14:15:54 -05001066 break;
1067 case SkPath::kConic_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -05001068 ctm.mapPoints(pts, 3);
Brian Salomon66085ed2017-02-02 15:39:34 -05001069 w = iter.conicWeight();
Jim Van Vertha84898d2017-02-06 13:38:23 -05001070 // point at t = 1/2
Brian Salomon66085ed2017-02-02 15:39:34 -05001071 curvePoint.fX = 0.25f*pts[0].fX + w*0.5f*pts[1].fX + 0.25f*pts[2].fX;
1072 curvePoint.fY = 0.25f*pts[0].fY + w*0.5f*pts[1].fY + 0.25f*pts[2].fY;
1073 curvePoint *= SkScalarInvert(0.5f + 0.5f*w);
1074 *fClipPolygon.push() = curvePoint;
Brian Salomon66085ed2017-02-02 15:39:34 -05001075 *fClipPolygon.push() = pts[2];
Brian Salomonab664fa2017-03-24 16:07:20 +00001076 this->handleConic(shadowTransform, pts, w);
Jim Van Verth91af7272017-01-27 14:15:54 -05001077 break;
1078 case SkPath::kCubic_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -05001079 ctm.mapPoints(pts, 4);
Brian Salomon66085ed2017-02-02 15:39:34 -05001080 // point at t = 5/16
1081 curvePoint.fX = kA*pts[0].fX + kB*pts[1].fX + kC*pts[2].fX + kD*pts[3].fX;
1082 curvePoint.fY = kA*pts[0].fY + kB*pts[1].fY + kC*pts[2].fY + kD*pts[3].fY;
1083 *fClipPolygon.push() = curvePoint;
Brian Salomon66085ed2017-02-02 15:39:34 -05001084 // point at t = 11/16
1085 curvePoint.fX = kD*pts[0].fX + kC*pts[1].fX + kB*pts[2].fX + kA*pts[3].fX;
1086 curvePoint.fY = kD*pts[0].fY + kC*pts[1].fY + kB*pts[2].fY + kA*pts[3].fY;
1087 *fClipPolygon.push() = curvePoint;
Brian Salomon66085ed2017-02-02 15:39:34 -05001088 *fClipPolygon.push() = pts[3];
Brian Salomonab664fa2017-03-24 16:07:20 +00001089 this->handleCubic(shadowTransform, pts);
Jim Van Verth91af7272017-01-27 14:15:54 -05001090 break;
Brian Salomonab664fa2017-03-24 16:07:20 +00001091 case SkPath::kMove_Verb:
Jim Van Verth91af7272017-01-27 14:15:54 -05001092 case SkPath::kClose_Verb:
Brian Salomonab664fa2017-03-24 16:07:20 +00001093 case SkPath::kDone_Verb:
Jim Van Verth91af7272017-01-27 14:15:54 -05001094 break;
1095 default:
1096 SkDEBUGFAIL("unknown verb");
1097 }
1098 }
1099
Brian Salomonab664fa2017-03-24 16:07:20 +00001100 // finish centroid
1101 if (fPathPolygon.count() > 0) {
1102 SkPoint currPoint = fPathPolygon[fPathPolygon.count() - 1];
1103 SkPoint nextPoint = fPathPolygon[0];
1104 SkScalar quadArea = currPoint.cross(nextPoint);
1105 fCentroid.fX += (currPoint.fX + nextPoint.fX) * quadArea;
1106 fCentroid.fY += (currPoint.fY + nextPoint.fY) * quadArea;
1107 fArea += quadArea;
1108 fCentroid *= SK_Scalar1 / (3 * fArea);
1109 }
1110
1111 fCurrClipPoint = fClipPolygon.count() - 1;
Brian Salomon66085ed2017-02-02 15:39:34 -05001112}
1113
Brian Salomonab664fa2017-03-24 16:07:20 +00001114void SkSpotShadowTessellator::computeClipVectorsAndTestCentroid() {
Brian Salomon66085ed2017-02-02 15:39:34 -05001115 SkASSERT(fClipPolygon.count() >= 3);
Brian Salomon66085ed2017-02-02 15:39:34 -05001116
Brian Salomonab664fa2017-03-24 16:07:20 +00001117 // init clip vectors
Brian Salomon66085ed2017-02-02 15:39:34 -05001118 SkVector v0 = fClipPolygon[1] - fClipPolygon[0];
1119 *fClipVectors.push() = v0;
Brian Salomon66085ed2017-02-02 15:39:34 -05001120
1121 // init centroid check
1122 bool hiddenCentroid = true;
Brian Salomonab664fa2017-03-24 16:07:20 +00001123 SkVector v1 = fCentroid - fClipPolygon[0];
Brian Salomon66085ed2017-02-02 15:39:34 -05001124 SkScalar initCross = v0.cross(v1);
1125
1126 for (int p = 1; p < fClipPolygon.count(); ++p) {
Brian Salomonab664fa2017-03-24 16:07:20 +00001127 // add to clip vectors
Brian Salomon66085ed2017-02-02 15:39:34 -05001128 v0 = fClipPolygon[(p + 1) % fClipPolygon.count()] - fClipPolygon[p];
1129 *fClipVectors.push() = v0;
Brian Salomon66085ed2017-02-02 15:39:34 -05001130 // Determine if transformed centroid is inside clipPolygon.
Brian Salomonab664fa2017-03-24 16:07:20 +00001131 v1 = fCentroid - fClipPolygon[p];
Brian Salomon66085ed2017-02-02 15:39:34 -05001132 if (initCross*v0.cross(v1) <= 0) {
1133 hiddenCentroid = false;
1134 }
1135 }
1136 SkASSERT(fClipVectors.count() == fClipPolygon.count());
1137
Brian Salomonab664fa2017-03-24 16:07:20 +00001138 fTransparent = fTransparent || !hiddenCentroid;
Brian Salomon66085ed2017-02-02 15:39:34 -05001139}
1140
1141bool SkSpotShadowTessellator::clipUmbraPoint(const SkPoint& umbraPoint, const SkPoint& centroid,
1142 SkPoint* clipPoint) {
1143 SkVector segmentVector = centroid - umbraPoint;
1144
Brian Salomonab664fa2017-03-24 16:07:20 +00001145 int startClipPoint = fCurrClipPoint;
Brian Salomon66085ed2017-02-02 15:39:34 -05001146 do {
Brian Salomonab664fa2017-03-24 16:07:20 +00001147 SkVector dp = umbraPoint - fClipPolygon[fCurrClipPoint];
1148 SkScalar denom = fClipVectors[fCurrClipPoint].cross(segmentVector);
Brian Salomon66085ed2017-02-02 15:39:34 -05001149 SkScalar t_num = dp.cross(segmentVector);
1150 // if line segments are nearly parallel
1151 if (SkScalarNearlyZero(denom)) {
1152 // and collinear
1153 if (SkScalarNearlyZero(t_num)) {
1154 return false;
1155 }
1156 // otherwise are separate, will try the next poly segment
1157 // else if crossing lies within poly segment
1158 } else if (t_num >= 0 && t_num <= denom) {
Brian Salomonab664fa2017-03-24 16:07:20 +00001159 SkScalar s_num = dp.cross(fClipVectors[fCurrClipPoint]);
Brian Salomon66085ed2017-02-02 15:39:34 -05001160 // if umbra point is inside the clip polygon
Jim Van Verthda965502017-04-11 15:29:14 -04001161 if (s_num >= 0 && s_num <= denom) {
Brian Salomon66085ed2017-02-02 15:39:34 -05001162 segmentVector *= s_num/denom;
1163 *clipPoint = umbraPoint + segmentVector;
1164 return true;
1165 }
1166 }
Brian Salomonab664fa2017-03-24 16:07:20 +00001167 fCurrClipPoint = (fCurrClipPoint + 1) % fClipPolygon.count();
1168 } while (fCurrClipPoint != startClipPoint);
Brian Salomon66085ed2017-02-02 15:39:34 -05001169
1170 return false;
Jim Van Verth91af7272017-01-27 14:15:54 -05001171}
1172
Brian Salomonab664fa2017-03-24 16:07:20 +00001173int SkSpotShadowTessellator::getClosestUmbraPoint(const SkPoint& p) {
Cary Clarkdf429f32017-11-08 11:44:31 -05001174 SkScalar minDistance = SkPointPriv::DistanceToSqd(p, fUmbraPolygon[fCurrUmbraPoint]);
Brian Salomonab664fa2017-03-24 16:07:20 +00001175 int index = fCurrUmbraPoint;
1176 int dir = 1;
1177 int next = (index + dir) % fUmbraPolygon.count();
1178
1179 // init travel direction
Cary Clarkdf429f32017-11-08 11:44:31 -05001180 SkScalar distance = SkPointPriv::DistanceToSqd(p, fUmbraPolygon[next]);
Brian Salomonab664fa2017-03-24 16:07:20 +00001181 if (distance < minDistance) {
1182 index = next;
1183 minDistance = distance;
1184 } else {
1185 dir = fUmbraPolygon.count()-1;
1186 }
1187
1188 // iterate until we find a point that increases the distance
1189 next = (index + dir) % fUmbraPolygon.count();
Cary Clarkdf429f32017-11-08 11:44:31 -05001190 distance = SkPointPriv::DistanceToSqd(p, fUmbraPolygon[next]);
Brian Salomonab664fa2017-03-24 16:07:20 +00001191 while (distance < minDistance) {
1192 index = next;
1193 minDistance = distance;
1194 next = (index + dir) % fUmbraPolygon.count();
Cary Clarkdf429f32017-11-08 11:44:31 -05001195 distance = SkPointPriv::DistanceToSqd(p, fUmbraPolygon[next]);
Brian Salomonab664fa2017-03-24 16:07:20 +00001196 }
1197
1198 fCurrUmbraPoint = index;
1199 return index;
1200}
1201
Jim Van Verthefe3ded2017-01-30 13:11:45 -05001202void SkSpotShadowTessellator::mapPoints(SkScalar scale, const SkVector& xlate,
Jim Van Verth91af7272017-01-27 14:15:54 -05001203 SkPoint* pts, int count) {
1204 // TODO: vectorize
1205 for (int i = 0; i < count; ++i) {
1206 pts[i] *= scale;
1207 pts[i] += xlate;
1208 }
1209}
1210
Brian Salomonab664fa2017-03-24 16:07:20 +00001211static bool duplicate_pt(const SkPoint& p0, const SkPoint& p1) {
1212 static constexpr SkScalar kClose = (SK_Scalar1 / 16);
1213 static constexpr SkScalar kCloseSqd = kClose*kClose;
1214
Cary Clarkdf429f32017-11-08 11:44:31 -05001215 SkScalar distSq = SkPointPriv::DistanceToSqd(p0, p1);
Brian Salomonab664fa2017-03-24 16:07:20 +00001216 return distSq < kCloseSqd;
1217}
1218
Jim Van Verthb55eb282017-07-18 14:13:45 -04001219static SkScalar perp_dot(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
Brian Salomonab664fa2017-03-24 16:07:20 +00001220 SkVector v0 = p1 - p0;
1221 SkVector v1 = p2 - p0;
Jim Van Verthb55eb282017-07-18 14:13:45 -04001222 return v0.cross(v1);
1223}
1224
1225static bool is_collinear(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
1226 return (SkScalarNearlyZero(perp_dot(p0, p1, p2)));
Brian Salomonab664fa2017-03-24 16:07:20 +00001227}
1228
Jim Van Verthefe3ded2017-01-30 13:11:45 -05001229void SkSpotShadowTessellator::handleLine(const SkPoint& p) {
Brian Salomonab664fa2017-03-24 16:07:20 +00001230 // remove coincident points and add to centroid
1231 if (fPathPolygon.count() > 0) {
1232 const SkPoint& lastPoint = fPathPolygon[fPathPolygon.count() - 1];
1233 if (duplicate_pt(p, lastPoint)) {
1234 return;
1235 }
1236 SkScalar quadArea = lastPoint.cross(p);
1237 fCentroid.fX += (p.fX + lastPoint.fX) * quadArea;
1238 fCentroid.fY += (p.fY + lastPoint.fY) * quadArea;
1239 fArea += quadArea;
1240 }
1241
1242 // try to remove collinear points
1243 if (fPathPolygon.count() > 1 && is_collinear(fPathPolygon[fPathPolygon.count()-2],
1244 fPathPolygon[fPathPolygon.count()-1],
1245 p)) {
1246 fPathPolygon[fPathPolygon.count() - 1] = p;
1247 } else {
1248 *fPathPolygon.push() = p;
1249 }
1250}
1251
Jim Van Verthb55eb282017-07-18 14:13:45 -04001252bool SkSpotShadowTessellator::handlePolyPoint(const SkPoint& p) {
Jim Van Verth91af7272017-01-27 14:15:54 -05001253 if (fInitPoints.count() < 2) {
1254 *fInitPoints.push() = p;
Jim Van Verthb55eb282017-07-18 14:13:45 -04001255 return true;
Jim Van Verth91af7272017-01-27 14:15:54 -05001256 }
1257
1258 if (fInitPoints.count() == 2) {
1259 // determine if cw or ccw
Jim Van Verthb55eb282017-07-18 14:13:45 -04001260 SkScalar perpDot = perp_dot(fInitPoints[0], fInitPoints[1], p);
Jim Van Verth91af7272017-01-27 14:15:54 -05001261 if (SkScalarNearlyZero(perpDot)) {
1262 // nearly parallel, just treat as straight line and continue
1263 fInitPoints[1] = p;
Jim Van Verthb55eb282017-07-18 14:13:45 -04001264 return true;
Jim Van Verth91af7272017-01-27 14:15:54 -05001265 }
1266
1267 // if perpDot > 0, winding is ccw
1268 fDirection = (perpDot > 0) ? -1 : 1;
1269
1270 // add first quad
Jim Van Verth76387852017-05-16 16:55:16 -04001271 if (!compute_normal(fInitPoints[0], fInitPoints[1], fDirection, &fFirstOutset)) {
Jim Van Verth91af7272017-01-27 14:15:54 -05001272 // first two points are incident, make the third point the second and continue
1273 fInitPoints[1] = p;
Jim Van Verthb55eb282017-07-18 14:13:45 -04001274 return true;
Jim Van Verth91af7272017-01-27 14:15:54 -05001275 }
1276
Jim Van Verth76387852017-05-16 16:55:16 -04001277 fFirstOutset *= fRadius;
Jim Van Verth91af7272017-01-27 14:15:54 -05001278 fFirstPoint = fInitPoints[0];
Jim Van Verth76387852017-05-16 16:55:16 -04001279 fFirstVertexIndex = fPositions.count();
1280 fPrevOutset = fFirstOutset;
Jim Van Verth91af7272017-01-27 14:15:54 -05001281 fPrevPoint = fFirstPoint;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -04001282 fPrevUmbraIndex = -1;
Jim Van Verth91af7272017-01-27 14:15:54 -05001283
Brian Salomon66085ed2017-02-02 15:39:34 -05001284 this->addInnerPoint(fFirstPoint);
Jim Van Verth76387852017-05-16 16:55:16 -04001285 fPrevUmbraIndex = fFirstVertexIndex;
Brian Salomon66085ed2017-02-02 15:39:34 -05001286
1287 if (!fTransparent) {
1288 SkPoint clipPoint;
Jim Van Verthb55eb282017-07-18 14:13:45 -04001289 bool isOutside = this->clipUmbraPoint(fPositions[fFirstVertexIndex],
1290 fCentroid, &clipPoint);
Brian Salomon66085ed2017-02-02 15:39:34 -05001291 if (isOutside) {
1292 *fPositions.push() = clipPoint;
1293 *fColors.push() = fUmbraColor;
1294 }
1295 fPrevUmbraOutside = isOutside;
1296 fFirstUmbraOutside = isOutside;
1297 }
1298
Jim Van Verth76387852017-05-16 16:55:16 -04001299 SkPoint newPoint = fFirstPoint + fFirstOutset;
Jim Van Verth91af7272017-01-27 14:15:54 -05001300 *fPositions.push() = newPoint;
1301 *fColors.push() = fPenumbraColor;
Jim Van Verth76387852017-05-16 16:55:16 -04001302 this->addEdge(fInitPoints[1], fFirstOutset);
Jim Van Verth91af7272017-01-27 14:15:54 -05001303
1304 // to ensure we skip this block next time
1305 *fInitPoints.push() = p;
1306 }
1307
Jim Van Verthb55eb282017-07-18 14:13:45 -04001308 // if concave, abort
1309 SkScalar perpDot = perp_dot(fInitPoints[1], fInitPoints[2], p);
1310 if (fDirection*perpDot > 0) {
1311 return false;
1312 }
1313
Jim Van Verth91af7272017-01-27 14:15:54 -05001314 SkVector normal;
Jim Van Verthda965502017-04-11 15:29:14 -04001315 if (compute_normal(fPrevPoint, p, fDirection, &normal)) {
1316 normal *= fRadius;
1317 this->addArc(normal, true);
1318 this->addEdge(p, normal);
Jim Van Verthb55eb282017-07-18 14:13:45 -04001319 fInitPoints[1] = fInitPoints[2];
1320 fInitPoints[2] = p;
Jim Van Verth91af7272017-01-27 14:15:54 -05001321 }
Jim Van Verthb55eb282017-07-18 14:13:45 -04001322
1323 return true;
Jim Van Verth91af7272017-01-27 14:15:54 -05001324}
1325
Brian Salomonab664fa2017-03-24 16:07:20 +00001326bool SkSpotShadowTessellator::addInnerPoint(const SkPoint& pathPoint) {
1327 SkPoint umbraPoint;
1328 if (!fValidUmbra) {
1329 SkVector v = fCentroid - pathPoint;
1330 v *= 0.95f;
1331 umbraPoint = pathPoint + v;
Jim Van Verth91af7272017-01-27 14:15:54 -05001332 } else {
Brian Salomonab664fa2017-03-24 16:07:20 +00001333 umbraPoint = fUmbraPolygon[this->getClosestUmbraPoint(pathPoint)];
Jim Van Verth91af7272017-01-27 14:15:54 -05001334 }
Brian Salomon66085ed2017-02-02 15:39:34 -05001335
Jim Van Verth91af7272017-01-27 14:15:54 -05001336 fPrevPoint = pathPoint;
Brian Salomonab664fa2017-03-24 16:07:20 +00001337
1338 // merge "close" points
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -04001339 if (fPrevUmbraIndex == -1 ||
Brian Salomonab664fa2017-03-24 16:07:20 +00001340 !duplicate_pt(umbraPoint, fPositions[fPrevUmbraIndex])) {
1341 *fPositions.push() = umbraPoint;
1342 *fColors.push() = fUmbraColor;
1343
1344 return false;
1345 } else {
1346 return true;
1347 }
Jim Van Verth91af7272017-01-27 14:15:54 -05001348}
1349
Jim Van Verthefe3ded2017-01-30 13:11:45 -05001350void SkSpotShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVector& nextNormal) {
Brian Salomon66085ed2017-02-02 15:39:34 -05001351 // add next umbra point
Brian Salomonab664fa2017-03-24 16:07:20 +00001352 bool duplicate = this->addInnerPoint(nextPoint);
1353 int prevPenumbraIndex = duplicate ? fPositions.count()-1 : fPositions.count()-2;
1354 int currUmbraIndex = duplicate ? fPrevUmbraIndex : fPositions.count()-1;
Brian Salomon66085ed2017-02-02 15:39:34 -05001355
Brian Salomonab664fa2017-03-24 16:07:20 +00001356 if (!duplicate) {
1357 // add to center fan if transparent or centroid showing
1358 if (fTransparent) {
1359 *fIndices.push() = 0;
1360 *fIndices.push() = fPrevUmbraIndex;
1361 *fIndices.push() = currUmbraIndex;
1362 // otherwise add to clip ring
1363 } else {
Brian Salomon66085ed2017-02-02 15:39:34 -05001364 SkPoint clipPoint;
Brian Salomonab664fa2017-03-24 16:07:20 +00001365 bool isOutside = this->clipUmbraPoint(fPositions[currUmbraIndex], fCentroid,
1366 &clipPoint);
Brian Salomon66085ed2017-02-02 15:39:34 -05001367 if (isOutside) {
1368 *fPositions.push() = clipPoint;
1369 *fColors.push() = fUmbraColor;
1370
1371 *fIndices.push() = fPrevUmbraIndex;
1372 *fIndices.push() = currUmbraIndex;
1373 *fIndices.push() = currUmbraIndex + 1;
1374 if (fPrevUmbraOutside) {
1375 // fill out quad
1376 *fIndices.push() = fPrevUmbraIndex;
1377 *fIndices.push() = currUmbraIndex + 1;
1378 *fIndices.push() = fPrevUmbraIndex + 1;
1379 }
1380 } else if (fPrevUmbraOutside) {
1381 // add tri
1382 *fIndices.push() = fPrevUmbraIndex;
1383 *fIndices.push() = currUmbraIndex;
1384 *fIndices.push() = fPrevUmbraIndex + 1;
1385 }
1386 fPrevUmbraOutside = isOutside;
1387 }
1388 }
1389
1390 // add next penumbra point and quad
Jim Van Verth91af7272017-01-27 14:15:54 -05001391 SkPoint newPoint = nextPoint + nextNormal;
1392 *fPositions.push() = newPoint;
1393 *fColors.push() = fPenumbraColor;
1394
Brian Salomonab664fa2017-03-24 16:07:20 +00001395 if (!duplicate) {
1396 *fIndices.push() = fPrevUmbraIndex;
1397 *fIndices.push() = prevPenumbraIndex;
1398 *fIndices.push() = currUmbraIndex;
1399 }
Jim Van Verth91af7272017-01-27 14:15:54 -05001400
Brian Salomon66085ed2017-02-02 15:39:34 -05001401 *fIndices.push() = prevPenumbraIndex;
Jim Van Verth91af7272017-01-27 14:15:54 -05001402 *fIndices.push() = fPositions.count() - 1;
Brian Salomon66085ed2017-02-02 15:39:34 -05001403 *fIndices.push() = currUmbraIndex;
Jim Van Verth91af7272017-01-27 14:15:54 -05001404
Brian Salomon66085ed2017-02-02 15:39:34 -05001405 fPrevUmbraIndex = currUmbraIndex;
Jim Van Verth76387852017-05-16 16:55:16 -04001406 fPrevOutset = nextNormal;
Jim Van Verth91af7272017-01-27 14:15:54 -05001407}
Brian Salomon958fbc42017-01-30 17:01:28 -05001408
1409///////////////////////////////////////////////////////////////////////////////////////////////////
1410
Brian Salomonaff27a22017-02-06 15:47:44 -05001411sk_sp<SkVertices> SkShadowTessellator::MakeAmbient(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -04001412 const SkPoint3& zPlane, bool transparent) {
Mike Reed9d5c6742018-03-06 10:31:27 -05001413 if (!path.isFinite() || !path.isConvex() || !ctm.isFinite() || !zPlane.isFinite()) {
1414 return nullptr;
1415 }
Jim Van Verthe308a122017-05-08 14:19:30 -04001416 SkAmbientShadowTessellator ambientTess(path, ctm, zPlane, transparent);
Brian Salomonaff27a22017-02-06 15:47:44 -05001417 return ambientTess.releaseVertices();
Brian Salomon958fbc42017-01-30 17:01:28 -05001418}
1419
Brian Salomonaff27a22017-02-06 15:47:44 -05001420sk_sp<SkVertices> SkShadowTessellator::MakeSpot(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -04001421 const SkPoint3& zPlane, const SkPoint3& lightPos,
Jim Van Verth060d9822017-05-04 09:58:17 -04001422 SkScalar lightRadius, bool transparent) {
Mike Reed9d5c6742018-03-06 10:31:27 -05001423 if (!path.isFinite() || !path.isConvex() || !ctm.isFinite() || !zPlane.isFinite() ||
1424 !lightPos.isFinite() || !SkScalarIsFinite(lightRadius) || !(lightRadius > 0)) {
1425 return nullptr;
1426 }
Jim Van Verthe308a122017-05-08 14:19:30 -04001427 SkSpotShadowTessellator spotTess(path, ctm, zPlane, lightPos, lightRadius, transparent);
Brian Salomonaff27a22017-02-06 15:47:44 -05001428 return spotTess.releaseVertices();
Brian Salomon958fbc42017-01-30 17:01:28 -05001429}