blob: c84490f6b9e1a810a44117bf086f459e09fd04bf [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"
Brian Salomon66085ed2017-02-02 15:39:34 -05009#include "SkColorPriv.h"
Jim Van Verth58abc9e2017-01-25 11:05:01 -050010#include "SkGeometry.h"
Brian Salomonab664fa2017-03-24 16:07:20 +000011#include "SkInsetConvexPolygon.h"
Jim Van Verth85dc96b2017-01-30 14:49:21 -050012#include "SkPath.h"
Brian Salomonaff27a22017-02-06 15:47:44 -050013#include "SkVertices.h"
Jim Van Verth85dc96b2017-01-30 14:49:21 -050014
15#if SK_SUPPORT_GPU
16#include "GrPathUtils.h"
17#endif
Jim Van Verth58abc9e2017-01-25 11:05:01 -050018
Brian Salomon958fbc42017-01-30 17:01:28 -050019
Jim Van Vertha84898d2017-02-06 13:38:23 -050020/**
21 * Base class
22 */
Brian Salomonaff27a22017-02-06 15:47:44 -050023class SkBaseShadowTessellator {
Brian Salomon958fbc42017-01-30 17:01:28 -050024public:
Jim Van Verthe308a122017-05-08 14:19:30 -040025 SkBaseShadowTessellator(const SkPoint3& zPlaneParams, bool transparent);
Brian Salomonaff27a22017-02-06 15:47:44 -050026 virtual ~SkBaseShadowTessellator() {}
Brian Salomon958fbc42017-01-30 17:01:28 -050027
Brian Salomonaff27a22017-02-06 15:47:44 -050028 sk_sp<SkVertices> releaseVertices() {
29 if (!fSucceeded) {
30 return nullptr;
31 }
Mike Reed887cdf12017-04-03 11:11:09 -040032 return SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode, this->vertexCount(),
Mike Reed97eb4fe2017-03-14 12:04:16 -040033 fPositions.begin(), nullptr, fColors.begin(),
34 this->indexCount(), fIndices.begin());
Brian Salomon1a8b79a2017-01-31 11:26:26 -050035 }
Brian Salomon958fbc42017-01-30 17:01:28 -050036
Jim Van Vertha84898d2017-02-06 13:38:23 -050037protected:
Jim Van Verthda965502017-04-11 15:29:14 -040038 static constexpr auto kMinHeight = 0.1f;
39
Brian Salomonaff27a22017-02-06 15:47:44 -050040 int vertexCount() const { return fPositions.count(); }
41 int indexCount() const { return fIndices.count(); }
42
Jim Van Verthda965502017-04-11 15:29:14 -040043 bool setZOffset(const SkRect& bounds, bool perspective);
44
Jim Van Vertha84898d2017-02-06 13:38:23 -050045 virtual void handleLine(const SkPoint& p) = 0;
46 void handleLine(const SkMatrix& m, SkPoint* p);
Brian Salomon958fbc42017-01-30 17:01:28 -050047
48 void handleQuad(const SkPoint pts[3]);
Jim Van Vertha84898d2017-02-06 13:38:23 -050049 void handleQuad(const SkMatrix& m, SkPoint pts[3]);
Brian Salomon958fbc42017-01-30 17:01:28 -050050
Jim Van Vertha84898d2017-02-06 13:38:23 -050051 void handleCubic(const SkMatrix& m, SkPoint pts[4]);
Brian Salomon958fbc42017-01-30 17:01:28 -050052
Jim Van Vertha84898d2017-02-06 13:38:23 -050053 void handleConic(const SkMatrix& m, SkPoint pts[3], SkScalar w);
Brian Salomon958fbc42017-01-30 17:01:28 -050054
Jim Van Verthda965502017-04-11 15:29:14 -040055 bool setTransformedHeightFunc(const SkMatrix& ctm);
Brian Salomon958fbc42017-01-30 17:01:28 -050056
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -040057 bool addArc(const SkVector& nextNormal, bool finishArc);
Jim Van Verthb4366552017-03-27 14:25:29 -040058
Jim Van Verthe308a122017-05-08 14:19:30 -040059 SkScalar heightFunc(SkScalar x, SkScalar y) {
60 return fZPlaneParams.fX*x + fZPlaneParams.fY*y + fZPlaneParams.fZ;
61 }
62
63 SkPoint3 fZPlaneParams;
Jim Van Verthda965502017-04-11 15:29:14 -040064 std::function<SkScalar(const SkPoint&)> fTransformedHeightFunc;
65 SkScalar fZOffset;
66 // members for perspective height function
Jim Van Verth4c9b8932017-05-15 13:49:21 -040067 SkPoint3 fTransformedZParams;
Jim Van Verthda965502017-04-11 15:29:14 -040068 SkScalar fPartialDeterminants[3];
69
70 // first two points
Brian Salomon958fbc42017-01-30 17:01:28 -050071 SkTDArray<SkPoint> fInitPoints;
72 // temporary buffer
73 SkTDArray<SkPoint> fPointBuffer;
Brian Salomon0dda9cb2017-02-03 10:33:25 -050074
Jim Van Vertha84898d2017-02-06 13:38:23 -050075 SkTDArray<SkPoint> fPositions;
76 SkTDArray<SkColor> fColors;
77 SkTDArray<uint16_t> fIndices;
78
Jim Van Verth76387852017-05-16 16:55:16 -040079 int fFirstVertexIndex;
80 SkVector fFirstOutset;
Jim Van Vertha84898d2017-02-06 13:38:23 -050081 SkPoint fFirstPoint;
82
Brian Salomon0dda9cb2017-02-03 10:33:25 -050083 bool fSucceeded;
Jim Van Vertha84898d2017-02-06 13:38:23 -050084 bool fTransparent;
85
86 SkColor fUmbraColor;
87 SkColor fPenumbraColor;
88
89 SkScalar fRadius;
90 SkScalar fDirection;
91 int fPrevUmbraIndex;
Jim Van Verth76387852017-05-16 16:55:16 -040092 SkVector fPrevOutset;
Jim Van Vertha84898d2017-02-06 13:38:23 -050093 SkPoint fPrevPoint;
Brian Salomon958fbc42017-01-30 17:01:28 -050094};
95
Jim Van Verthda965502017-04-11 15:29:14 -040096static bool compute_normal(const SkPoint& p0, const SkPoint& p1, SkScalar dir,
Jim Van Verthbce74962017-01-25 09:39:46 -050097 SkVector* newNormal) {
98 SkVector normal;
99 // compute perpendicular
100 normal.fX = p0.fY - p1.fY;
101 normal.fY = p1.fX - p0.fX;
Jim Van Verthda965502017-04-11 15:29:14 -0400102 normal *= dir;
Jim Van Verthbce74962017-01-25 09:39:46 -0500103 if (!normal.normalize()) {
104 return false;
105 }
Jim Van Verthbce74962017-01-25 09:39:46 -0500106 *newNormal = normal;
107 return true;
108}
109
110static void compute_radial_steps(const SkVector& v1, const SkVector& v2, SkScalar r,
111 SkScalar* rotSin, SkScalar* rotCos, int* n) {
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400112 const SkScalar kRecipPixelsPerArcSegment = 0.125f;
Jim Van Verthbce74962017-01-25 09:39:46 -0500113
114 SkScalar rCos = v1.dot(v2);
115 SkScalar rSin = v1.cross(v2);
116 SkScalar theta = SkScalarATan2(rSin, rCos);
117
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400118 int steps = SkScalarFloorToInt(r*theta*kRecipPixelsPerArcSegment);
Jim Van Verthbce74962017-01-25 09:39:46 -0500119
120 SkScalar dTheta = theta / steps;
121 *rotSin = SkScalarSinCos(dTheta, rotCos);
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400122 *n = steps;
Jim Van Verthbce74962017-01-25 09:39:46 -0500123}
124
Jim Van Verthe308a122017-05-08 14:19:30 -0400125SkBaseShadowTessellator::SkBaseShadowTessellator(const SkPoint3& zPlaneParams, bool transparent)
126 : fZPlaneParams(zPlaneParams)
Jim Van Verthda965502017-04-11 15:29:14 -0400127 , fZOffset(0)
Jim Van Verth76387852017-05-16 16:55:16 -0400128 , fFirstVertexIndex(-1)
Brian Salomonaff27a22017-02-06 15:47:44 -0500129 , fSucceeded(false)
130 , fTransparent(transparent)
Brian Salomonaff27a22017-02-06 15:47:44 -0500131 , fDirection(1)
132 , fPrevUmbraIndex(-1) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500133 fInitPoints.setReserve(3);
134
135 // child classes will set reserve for positions, colors and indices
136}
137
Jim Van Verthda965502017-04-11 15:29:14 -0400138bool SkBaseShadowTessellator::setZOffset(const SkRect& bounds, bool perspective) {
Jim Van Verthe308a122017-05-08 14:19:30 -0400139 SkScalar minZ = this->heightFunc(bounds.fLeft, bounds.fTop);
Jim Van Verthda965502017-04-11 15:29:14 -0400140 if (perspective) {
Jim Van Verthe308a122017-05-08 14:19:30 -0400141 SkScalar z = this->heightFunc(bounds.fLeft, bounds.fBottom);
Jim Van Verthda965502017-04-11 15:29:14 -0400142 if (z < minZ) {
143 minZ = z;
144 }
Jim Van Verthe308a122017-05-08 14:19:30 -0400145 z = this->heightFunc(bounds.fRight, bounds.fTop);
Jim Van Verthda965502017-04-11 15:29:14 -0400146 if (z < minZ) {
147 minZ = z;
148 }
Jim Van Verthe308a122017-05-08 14:19:30 -0400149 z = this->heightFunc(bounds.fRight, bounds.fBottom);
Jim Van Verthda965502017-04-11 15:29:14 -0400150 if (z < minZ) {
151 minZ = z;
152 }
153 }
154
155 if (minZ < kMinHeight) {
156 fZOffset = -minZ + kMinHeight;
157 return true;
158 }
159
160 return false;
161}
162
Jim Van Vertha84898d2017-02-06 13:38:23 -0500163// tesselation tolerance values, in device space pixels
Mike Kleinb8b51e62017-02-09 15:22:53 -0500164#if SK_SUPPORT_GPU
Jim Van Vertha84898d2017-02-06 13:38:23 -0500165static const SkScalar kQuadTolerance = 0.2f;
166static const SkScalar kCubicTolerance = 0.2f;
Mike Kleinb8b51e62017-02-09 15:22:53 -0500167#endif
Jim Van Vertha84898d2017-02-06 13:38:23 -0500168static const SkScalar kConicTolerance = 0.5f;
169
Brian Salomonaff27a22017-02-06 15:47:44 -0500170void SkBaseShadowTessellator::handleLine(const SkMatrix& m, SkPoint* p) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500171 m.mapPoints(p, 1);
172 this->handleLine(*p);
173}
174
Brian Salomonaff27a22017-02-06 15:47:44 -0500175void SkBaseShadowTessellator::handleQuad(const SkPoint pts[3]) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500176#if SK_SUPPORT_GPU
177 // TODO: Pull PathUtils out of Ganesh?
178 int maxCount = GrPathUtils::quadraticPointCount(pts, kQuadTolerance);
179 fPointBuffer.setReserve(maxCount);
180 SkPoint* target = fPointBuffer.begin();
181 int count = GrPathUtils::generateQuadraticPoints(pts[0], pts[1], pts[2],
182 kQuadTolerance, &target, maxCount);
183 fPointBuffer.setCount(count);
184 for (int i = 0; i < count; i++) {
185 this->handleLine(fPointBuffer[i]);
186 }
187#else
188 // for now, just to draw something
189 this->handleLine(pts[1]);
190 this->handleLine(pts[2]);
191#endif
192}
193
Brian Salomonaff27a22017-02-06 15:47:44 -0500194void SkBaseShadowTessellator::handleQuad(const SkMatrix& m, SkPoint pts[3]) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500195 m.mapPoints(pts, 3);
196 this->handleQuad(pts);
197}
198
Brian Salomonaff27a22017-02-06 15:47:44 -0500199void SkBaseShadowTessellator::handleCubic(const SkMatrix& m, SkPoint pts[4]) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500200 m.mapPoints(pts, 4);
201#if SK_SUPPORT_GPU
202 // TODO: Pull PathUtils out of Ganesh?
203 int maxCount = GrPathUtils::cubicPointCount(pts, kCubicTolerance);
204 fPointBuffer.setReserve(maxCount);
205 SkPoint* target = fPointBuffer.begin();
206 int count = GrPathUtils::generateCubicPoints(pts[0], pts[1], pts[2], pts[3],
207 kCubicTolerance, &target, maxCount);
208 fPointBuffer.setCount(count);
209 for (int i = 0; i < count; i++) {
210 this->handleLine(fPointBuffer[i]);
211 }
212#else
213 // for now, just to draw something
214 this->handleLine(pts[1]);
215 this->handleLine(pts[2]);
216 this->handleLine(pts[3]);
217#endif
218}
219
Brian Salomonaff27a22017-02-06 15:47:44 -0500220void SkBaseShadowTessellator::handleConic(const SkMatrix& m, SkPoint pts[3], SkScalar w) {
Jim Van Verthda965502017-04-11 15:29:14 -0400221 if (m.hasPerspective()) {
222 w = SkConic::TransformW(pts, w, m);
223 }
Jim Van Vertha84898d2017-02-06 13:38:23 -0500224 m.mapPoints(pts, 3);
225 SkAutoConicToQuads quadder;
226 const SkPoint* quads = quadder.computeQuads(pts, w, kConicTolerance);
227 SkPoint lastPoint = *(quads++);
228 int count = quadder.countQuads();
229 for (int i = 0; i < count; ++i) {
230 SkPoint quadPts[3];
231 quadPts[0] = lastPoint;
232 quadPts[1] = quads[0];
233 quadPts[2] = i == count - 1 ? pts[2] : quads[1];
234 this->handleQuad(quadPts);
235 lastPoint = quadPts[2];
236 quads += 2;
237 }
238}
239
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400240bool SkBaseShadowTessellator::addArc(const SkVector& nextNormal, bool finishArc) {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500241 // fill in fan from previous quad
242 SkScalar rotSin, rotCos;
243 int numSteps;
Jim Van Verth76387852017-05-16 16:55:16 -0400244 compute_radial_steps(fPrevOutset, nextNormal, fRadius, &rotSin, &rotCos, &numSteps);
245 SkVector prevNormal = fPrevOutset;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400246 for (int i = 0; i < numSteps-1; ++i) {
Jim Van Verthda965502017-04-11 15:29:14 -0400247 SkVector currNormal;
248 currNormal.fX = prevNormal.fX*rotCos - prevNormal.fY*rotSin;
249 currNormal.fY = prevNormal.fY*rotCos + prevNormal.fX*rotSin;
250 *fPositions.push() = fPrevPoint + currNormal;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500251 *fColors.push() = fPenumbraColor;
252 *fIndices.push() = fPrevUmbraIndex;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500253 *fIndices.push() = fPositions.count() - 1;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400254 *fIndices.push() = fPositions.count() - 2;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500255
Jim Van Verthda965502017-04-11 15:29:14 -0400256 prevNormal = currNormal;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500257 }
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400258 if (finishArc && numSteps) {
Jim Van Verthda965502017-04-11 15:29:14 -0400259 *fPositions.push() = fPrevPoint + nextNormal;
260 *fColors.push() = fPenumbraColor;
261 *fIndices.push() = fPrevUmbraIndex;
Jim Van Verthda965502017-04-11 15:29:14 -0400262 *fIndices.push() = fPositions.count() - 1;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400263 *fIndices.push() = fPositions.count() - 2;
Jim Van Verthda965502017-04-11 15:29:14 -0400264 }
Jim Van Verth76387852017-05-16 16:55:16 -0400265 fPrevOutset = nextNormal;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400266
267 return (numSteps > 0);
Jim Van Vertha84898d2017-02-06 13:38:23 -0500268}
269
Jim Van Verthda965502017-04-11 15:29:14 -0400270bool SkBaseShadowTessellator::setTransformedHeightFunc(const SkMatrix& ctm) {
Jim Van Verth4c9b8932017-05-15 13:49:21 -0400271 if (SkScalarNearlyZero(fZPlaneParams.fX) && SkScalarNearlyZero(fZPlaneParams.fY)) {
Jim Van Verthda965502017-04-11 15:29:14 -0400272 fTransformedHeightFunc = [this](const SkPoint& p) {
Jim Van Verthe308a122017-05-08 14:19:30 -0400273 return fZPlaneParams.fZ;
Jim Van Verthda965502017-04-11 15:29:14 -0400274 };
275 } else {
276 SkMatrix ctmInverse;
277 if (!ctm.invert(&ctmInverse)) {
278 return false;
279 }
Jim Van Verthda965502017-04-11 15:29:14 -0400280 // multiply by transpose
Jim Van Verth4c9b8932017-05-15 13:49:21 -0400281 fTransformedZParams = SkPoint3::Make(
Jim Van Verthe308a122017-05-08 14:19:30 -0400282 ctmInverse[SkMatrix::kMScaleX] * fZPlaneParams.fX +
283 ctmInverse[SkMatrix::kMSkewY] * fZPlaneParams.fY +
284 ctmInverse[SkMatrix::kMPersp0] * fZPlaneParams.fZ,
285
286 ctmInverse[SkMatrix::kMSkewX] * fZPlaneParams.fX +
287 ctmInverse[SkMatrix::kMScaleY] * fZPlaneParams.fY +
288 ctmInverse[SkMatrix::kMPersp1] * fZPlaneParams.fZ,
289
290 ctmInverse[SkMatrix::kMTransX] * fZPlaneParams.fX +
291 ctmInverse[SkMatrix::kMTransY] * fZPlaneParams.fY +
292 ctmInverse[SkMatrix::kMPersp2] * fZPlaneParams.fZ
293 );
Jim Van Verthda965502017-04-11 15:29:14 -0400294
Jim Van Verth4c9b8932017-05-15 13:49:21 -0400295 if (ctm.hasPerspective()) {
296 // We use Cramer's rule to solve for the W value for a given post-divide X and Y,
297 // so pre-compute those values that are independent of X and Y.
298 // W is det(ctmInverse)/(PD[0]*X + PD[1]*Y + PD[2])
299 fPartialDeterminants[0] = ctm[SkMatrix::kMSkewY] * ctm[SkMatrix::kMPersp1] -
300 ctm[SkMatrix::kMScaleY] * ctm[SkMatrix::kMPersp0];
301 fPartialDeterminants[1] = ctm[SkMatrix::kMPersp0] * ctm[SkMatrix::kMSkewX] -
302 ctm[SkMatrix::kMPersp1] * ctm[SkMatrix::kMScaleX];
303 fPartialDeterminants[2] = ctm[SkMatrix::kMScaleX] * ctm[SkMatrix::kMScaleY] -
304 ctm[SkMatrix::kMSkewX] * ctm[SkMatrix::kMSkewY];
305 SkScalar ctmDeterminant = ctm[SkMatrix::kMTransX] * fPartialDeterminants[0] +
306 ctm[SkMatrix::kMTransY] * fPartialDeterminants[1] +
307 ctm[SkMatrix::kMPersp2] * fPartialDeterminants[2];
Jim Van Verthda965502017-04-11 15:29:14 -0400308
Jim Van Verth4c9b8932017-05-15 13:49:21 -0400309 // Pre-bake the numerator of Cramer's rule into the zParams to avoid another multiply.
310 // TODO: this may introduce numerical instability, but I haven't seen any issues yet.
311 fTransformedZParams.fX *= ctmDeterminant;
312 fTransformedZParams.fY *= ctmDeterminant;
313 fTransformedZParams.fZ *= ctmDeterminant;
Jim Van Verthda965502017-04-11 15:29:14 -0400314
Jim Van Verth4c9b8932017-05-15 13:49:21 -0400315 fTransformedHeightFunc = [this](const SkPoint& p) {
316 SkScalar denom = p.fX * fPartialDeterminants[0] +
317 p.fY * fPartialDeterminants[1] +
318 fPartialDeterminants[2];
319 SkScalar w = SkScalarFastInvert(denom);
320 return fZOffset + w*(fTransformedZParams.fX * p.fX +
321 fTransformedZParams.fY * p.fY +
322 fTransformedZParams.fZ);
323 };
324 } else {
325 fTransformedHeightFunc = [this](const SkPoint& p) {
326 return fZOffset + fTransformedZParams.fX * p.fX +
327 fTransformedZParams.fY * p.fY + fTransformedZParams.fZ;
328 };
329 }
Jim Van Verthda965502017-04-11 15:29:14 -0400330 }
331
332 return true;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500333}
334
335
336//////////////////////////////////////////////////////////////////////////////////////////////////
337
Brian Salomonaff27a22017-02-06 15:47:44 -0500338class SkAmbientShadowTessellator : public SkBaseShadowTessellator {
Jim Van Vertha84898d2017-02-06 13:38:23 -0500339public:
340 SkAmbientShadowTessellator(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -0400341 const SkPoint3& zPlaneParams, bool transparent);
Jim Van Vertha84898d2017-02-06 13:38:23 -0500342
343private:
344 void handleLine(const SkPoint& p) override;
Jim Van Verthda965502017-04-11 15:29:14 -0400345 void addEdge(const SkVector& nextPoint, const SkVector& nextNormal);
Jim Van Vertha84898d2017-02-06 13:38:23 -0500346
Jim Van Verthda965502017-04-11 15:29:14 -0400347 static constexpr auto kHeightFactor = 1.0f / 128.0f;
348 static constexpr auto kGeomFactor = 64.0f;
349 static constexpr auto kMaxEdgeLenSqr = 20 * 20;
Jim Van Verth76387852017-05-16 16:55:16 -0400350 static constexpr auto kInsetFactor = -0.5f;
Jim Van Verthda965502017-04-11 15:29:14 -0400351
352 SkScalar offset(SkScalar z) {
353 return z * kHeightFactor * kGeomFactor;
354 }
355 SkColor umbraColor(SkScalar z) {
356 SkScalar umbraAlpha = SkScalarInvert((1.0f + SkTMax(z*kHeightFactor, 0.0f)));
Jim Van Verth060d9822017-05-04 09:58:17 -0400357 return SkColorSetARGB(umbraAlpha * 255.9999f, 0, 0, 0);
Jim Van Verthda965502017-04-11 15:29:14 -0400358 }
359
Jim Van Vertha84898d2017-02-06 13:38:23 -0500360 int fCentroidCount;
Jim Van Verth76387852017-05-16 16:55:16 -0400361 bool fSplitFirstEdge;
362 bool fSplitPreviousEdge;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500363
Brian Salomonaff27a22017-02-06 15:47:44 -0500364 typedef SkBaseShadowTessellator INHERITED;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500365};
366
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500367SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path,
Jim Van Vertha84898d2017-02-06 13:38:23 -0500368 const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -0400369 const SkPoint3& zPlaneParams,
Jim Van Verthbce74962017-01-25 09:39:46 -0500370 bool transparent)
Jim Van Verth76387852017-05-16 16:55:16 -0400371 : INHERITED(zPlaneParams, transparent)
372 , fSplitFirstEdge(false)
373 , fSplitPreviousEdge(false) {
Jim Van Verthda965502017-04-11 15:29:14 -0400374 // Set base colors
Jim Van Verthb4366552017-03-27 14:25:29 -0400375 SkScalar occluderHeight = heightFunc(0, 0);
Jim Van Verthb4366552017-03-27 14:25:29 -0400376 SkScalar umbraAlpha = SkScalarInvert((1.0f + SkTMax(occluderHeight*kHeightFactor, 0.0f)));
377 // umbraColor is the interior value, penumbraColor the exterior value.
378 // umbraAlpha is the factor that is linearly interpolated from outside to inside, and
379 // then "blurred" by the GrBlurredEdgeFP. It is then multiplied by fAmbientAlpha to get
380 // the final alpha.
Jim Van Verth060d9822017-05-04 09:58:17 -0400381 fUmbraColor = SkColorSetARGB(umbraAlpha * 255.9999f, 0, 0, 0);
382 fPenumbraColor = SkColorSetARGB(0, 0, 0, 0);
Jim Van Verthb4366552017-03-27 14:25:29 -0400383
Jim Van Verthda965502017-04-11 15:29:14 -0400384 // make sure we're not below the canvas plane
385 this->setZOffset(path.getBounds(), ctm.hasPerspective());
386
387 this->setTransformedHeightFunc(ctm);
388
Jim Van Verthbce74962017-01-25 09:39:46 -0500389 // Outer ring: 3*numPts
390 // Middle ring: numPts
391 fPositions.setReserve(4 * path.countPoints());
392 fColors.setReserve(4 * path.countPoints());
393 // Outer ring: 12*numPts
394 // Middle ring: 0
395 fIndices.setReserve(12 * path.countPoints());
396
Jim Van Verthbce74962017-01-25 09:39:46 -0500397 // walk around the path, tessellate and generate outer ring
398 // if original path is transparent, will accumulate sum of points for centroid
399 SkPath::Iter iter(path, true);
400 SkPoint pts[4];
401 SkPath::Verb verb;
402 if (fTransparent) {
403 *fPositions.push() = SkPoint::Make(0, 0);
Jim Van Verthb4366552017-03-27 14:25:29 -0400404 *fColors.push() = fUmbraColor;
Jim Van Verthbce74962017-01-25 09:39:46 -0500405 fCentroidCount = 0;
406 }
407 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
408 switch (verb) {
409 case SkPath::kLine_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -0500410 this->INHERITED::handleLine(ctm, &pts[1]);
Jim Van Verthbce74962017-01-25 09:39:46 -0500411 break;
412 case SkPath::kQuad_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -0500413 this->handleQuad(ctm, pts);
Jim Van Verthbce74962017-01-25 09:39:46 -0500414 break;
415 case SkPath::kCubic_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -0500416 this->handleCubic(ctm, pts);
Jim Van Verthbce74962017-01-25 09:39:46 -0500417 break;
418 case SkPath::kConic_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -0500419 this->handleConic(ctm, pts, iter.conicWeight());
Jim Van Verthbce74962017-01-25 09:39:46 -0500420 break;
421 case SkPath::kMove_Verb:
422 case SkPath::kClose_Verb:
423 case SkPath::kDone_Verb:
424 break;
425 }
426 }
427
Brian Salomon0dda9cb2017-02-03 10:33:25 -0500428 if (!this->indexCount()) {
429 return;
430 }
431
Jim Van Verth76387852017-05-16 16:55:16 -0400432 // Finish up
Jim Van Verthbce74962017-01-25 09:39:46 -0500433 SkVector normal;
Jim Van Verthda965502017-04-11 15:29:14 -0400434 if (compute_normal(fPrevPoint, fFirstPoint, fDirection, &normal)) {
435 SkScalar z = fTransformedHeightFunc(fPrevPoint);
436 fRadius = this->offset(z);
437 SkVector scaledNormal(normal);
438 scaledNormal *= fRadius;
439 this->addArc(scaledNormal, true);
Jim Van Verthbce74962017-01-25 09:39:46 -0500440
Jim Van Verth76387852017-05-16 16:55:16 -0400441 // fix-up the last and first umbra points
442 SkVector inset = normal;
443 // adding to an average, so multiply by an additional half
444 inset *= 0.5f*kInsetFactor;
445 fPositions[fPrevUmbraIndex] += inset;
446 fPositions[fFirstVertexIndex] += inset;
447 // we multiply by another half because now we're adding to an average of an average
448 inset *= 0.5f;
449 if (fSplitPreviousEdge) {
450 fPositions[fPrevUmbraIndex - 2] += inset;
451 }
452 if (fSplitFirstEdge) {
453 fPositions[fFirstVertexIndex + 2] += inset;
454 }
455
Jim Van Verthda965502017-04-11 15:29:14 -0400456 // set up for final edge
457 z = fTransformedHeightFunc(fFirstPoint);
458 normal *= this->offset(z);
Jim Van Verthbce74962017-01-25 09:39:46 -0500459
Jim Van Verthda965502017-04-11 15:29:14 -0400460 // make sure we don't end up with a sharp alpha edge along the quad diagonal
Jim Van Verth76387852017-05-16 16:55:16 -0400461 if (fColors[fPrevUmbraIndex] != fColors[fFirstVertexIndex] &&
Jim Van Verthda965502017-04-11 15:29:14 -0400462 fFirstPoint.distanceToSqd(fPositions[fPrevUmbraIndex]) > kMaxEdgeLenSqr) {
Jim Van Verth76387852017-05-16 16:55:16 -0400463 SkPoint centerPoint = fPositions[fPrevUmbraIndex] + fPositions[fFirstVertexIndex];
Jim Van Verthda965502017-04-11 15:29:14 -0400464 centerPoint *= 0.5f;
465 *fPositions.push() = centerPoint;
Jim Van Verth76387852017-05-16 16:55:16 -0400466 *fColors.push() = SkPMLerp(fColors[fFirstVertexIndex], fColors[fPrevUmbraIndex], 128);
467 centerPoint = fPositions[fPositions.count()-2] + fPositions[fFirstVertexIndex+1];
468 centerPoint *= 0.5f;
469 *fPositions.push() = centerPoint;
Jim Van Verthda965502017-04-11 15:29:14 -0400470 *fColors.push() = fPenumbraColor;
471
Jim Van Verth76387852017-05-16 16:55:16 -0400472 if (fColors[fPrevUmbraIndex] > fColors[fPositions.count() - 2]) {
473 *fIndices.push() = fPrevUmbraIndex;
474 *fIndices.push() = fPositions.count() - 3;
475 *fIndices.push() = fPositions.count() - 2;
Jim Van Verthda965502017-04-11 15:29:14 -0400476
Jim Van Verth76387852017-05-16 16:55:16 -0400477 *fIndices.push() = fPositions.count() - 3;
478 *fIndices.push() = fPositions.count() - 1;
479 *fIndices.push() = fPositions.count() - 2;
480 } else {
481 *fIndices.push() = fPrevUmbraIndex;
482 *fIndices.push() = fPositions.count() - 2;
483 *fIndices.push() = fPositions.count() - 1;
484
485 *fIndices.push() = fPrevUmbraIndex;
486 *fIndices.push() = fPositions.count() - 1;
487 *fIndices.push() = fPositions.count() - 3;
488 }
Jim Van Verthda965502017-04-11 15:29:14 -0400489
Jim Van Verth1c4c1142017-05-11 10:23:53 -0400490 // if transparent, add point to first one in array and add to center fan
491 if (fTransparent) {
492 fPositions[0] += centerPoint;
493 ++fCentroidCount;
494
495 *fIndices.push() = 0;
496 *fIndices.push() = fPrevUmbraIndex;
497 *fIndices.push() = fPositions.count() - 2;
498 }
499
Jim Van Verthda965502017-04-11 15:29:14 -0400500 fPrevUmbraIndex = fPositions.count() - 2;
501 }
502
503 // final edge
Jim Van Vertha84898d2017-02-06 13:38:23 -0500504 *fPositions.push() = fFirstPoint + normal;
Jim Van Verthbce74962017-01-25 09:39:46 -0500505 *fColors.push() = fPenumbraColor;
506
Jim Van Verth76387852017-05-16 16:55:16 -0400507 if (fColors[fPrevUmbraIndex] > fColors[fFirstVertexIndex]) {
Jim Van Verthda965502017-04-11 15:29:14 -0400508 *fIndices.push() = fPrevUmbraIndex;
509 *fIndices.push() = fPositions.count() - 2;
Jim Van Verth76387852017-05-16 16:55:16 -0400510 *fIndices.push() = fFirstVertexIndex;
Jim Van Verthbce74962017-01-25 09:39:46 -0500511
Jim Van Verthda965502017-04-11 15:29:14 -0400512 *fIndices.push() = fPositions.count() - 2;
513 *fIndices.push() = fPositions.count() - 1;
Jim Van Verth76387852017-05-16 16:55:16 -0400514 *fIndices.push() = fFirstVertexIndex;
Jim Van Verthda965502017-04-11 15:29:14 -0400515 } else {
516 *fIndices.push() = fPrevUmbraIndex;
517 *fIndices.push() = fPositions.count() - 2;
518 *fIndices.push() = fPositions.count() - 1;
519
520 *fIndices.push() = fPrevUmbraIndex;
521 *fIndices.push() = fPositions.count() - 1;
Jim Van Verth76387852017-05-16 16:55:16 -0400522 *fIndices.push() = fFirstVertexIndex;
Jim Van Verthda965502017-04-11 15:29:14 -0400523 }
Jim Van Verth76387852017-05-16 16:55:16 -0400524 fPrevOutset = normal;
Jim Van Verthbce74962017-01-25 09:39:46 -0500525 }
526
527 // finalize centroid
528 if (fTransparent) {
529 fPositions[0] *= SkScalarFastInvert(fCentroidCount);
Jim Van Verth1c4c1142017-05-11 10:23:53 -0400530 fColors[0] = this->umbraColor(fTransformedHeightFunc(fPositions[0]));
Jim Van Verthbce74962017-01-25 09:39:46 -0500531
532 *fIndices.push() = 0;
Brian Salomon66085ed2017-02-02 15:39:34 -0500533 *fIndices.push() = fPrevUmbraIndex;
Jim Van Verth76387852017-05-16 16:55:16 -0400534 *fIndices.push() = fFirstVertexIndex;
Jim Van Verthbce74962017-01-25 09:39:46 -0500535 }
536
537 // final fan
538 if (fPositions.count() >= 3) {
Jim Van Verth76387852017-05-16 16:55:16 -0400539 fPrevUmbraIndex = fFirstVertexIndex;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500540 fPrevPoint = fFirstPoint;
Jim Van Verthda965502017-04-11 15:29:14 -0400541 fRadius = this->offset(fTransformedHeightFunc(fPrevPoint));
Jim Van Verth76387852017-05-16 16:55:16 -0400542 if (this->addArc(fFirstOutset, false)) {
543 *fIndices.push() = fFirstVertexIndex;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400544 *fIndices.push() = fPositions.count() - 1;
Jim Van Verth76387852017-05-16 16:55:16 -0400545 *fIndices.push() = fFirstVertexIndex + 1;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400546 } else {
547 // arc is too small, set the first penumbra point to be the same position
548 // as the last one
Jim Van Verth76387852017-05-16 16:55:16 -0400549 fPositions[fFirstVertexIndex + 1] = fPositions[fPositions.count() - 1];
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400550 }
Jim Van Verthbce74962017-01-25 09:39:46 -0500551 }
Brian Salomon0dda9cb2017-02-03 10:33:25 -0500552 fSucceeded = true;
Jim Van Verthbce74962017-01-25 09:39:46 -0500553}
554
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500555void SkAmbientShadowTessellator::handleLine(const SkPoint& p) {
Jim Van Verthbce74962017-01-25 09:39:46 -0500556 if (fInitPoints.count() < 2) {
557 *fInitPoints.push() = p;
558 return;
559 }
560
561 if (fInitPoints.count() == 2) {
562 // determine if cw or ccw
563 SkVector v0 = fInitPoints[1] - fInitPoints[0];
564 SkVector v1 = p - fInitPoints[0];
565 SkScalar perpDot = v0.fX*v1.fY - v0.fY*v1.fX;
566 if (SkScalarNearlyZero(perpDot)) {
567 // nearly parallel, just treat as straight line and continue
568 fInitPoints[1] = p;
569 return;
570 }
571
572 // if perpDot > 0, winding is ccw
573 fDirection = (perpDot > 0) ? -1 : 1;
574
575 // add first quad
Jim Van Verthda965502017-04-11 15:29:14 -0400576 SkVector normal;
577 if (!compute_normal(fInitPoints[0], fInitPoints[1], fDirection, &normal)) {
Jim Van Verthbce74962017-01-25 09:39:46 -0500578 // first two points are incident, make the third point the second and continue
579 fInitPoints[1] = p;
580 return;
581 }
582
Jim Van Vertha84898d2017-02-06 13:38:23 -0500583 fFirstPoint = fInitPoints[0];
Jim Van Verth76387852017-05-16 16:55:16 -0400584 fFirstVertexIndex = fPositions.count();
Jim Van Verthda965502017-04-11 15:29:14 -0400585 SkScalar z = fTransformedHeightFunc(fFirstPoint);
Jim Van Verth76387852017-05-16 16:55:16 -0400586 fFirstOutset = normal;
587 fFirstOutset *= this->offset(z);
Jim Van Verthda965502017-04-11 15:29:14 -0400588
Jim Van Verth76387852017-05-16 16:55:16 -0400589 fPrevOutset = fFirstOutset;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500590 fPrevPoint = fFirstPoint;
Jim Van Verth76387852017-05-16 16:55:16 -0400591 fPrevUmbraIndex = fFirstVertexIndex;
Jim Van Verthbce74962017-01-25 09:39:46 -0500592
Jim Van Verthda965502017-04-11 15:29:14 -0400593 *fPositions.push() = fFirstPoint;
594 *fColors.push() = this->umbraColor(z);
Jim Van Verth76387852017-05-16 16:55:16 -0400595 *fPositions.push() = fFirstPoint + fFirstOutset;
Jim Van Verthbce74962017-01-25 09:39:46 -0500596 *fColors.push() = fPenumbraColor;
597 if (fTransparent) {
Jim Van Verthda965502017-04-11 15:29:14 -0400598 fPositions[0] += fFirstPoint;
Jim Van Verthbce74962017-01-25 09:39:46 -0500599 fCentroidCount = 1;
600 }
Jim Van Verthda965502017-04-11 15:29:14 -0400601
602 // add the first quad
603 z = fTransformedHeightFunc(fInitPoints[1]);
604 fRadius = this->offset(z);
605 fUmbraColor = this->umbraColor(z);
Jim Van Verthda965502017-04-11 15:29:14 -0400606 this->addEdge(fInitPoints[1], normal);
Jim Van Verthbce74962017-01-25 09:39:46 -0500607
608 // to ensure we skip this block next time
609 *fInitPoints.push() = p;
610 }
611
612 SkVector normal;
Jim Van Verth76387852017-05-16 16:55:16 -0400613 if (compute_normal(fPrevPoint, p, fDirection, &normal)) {
Jim Van Verthda965502017-04-11 15:29:14 -0400614 SkVector scaledNormal = normal;
615 scaledNormal *= fRadius;
616 this->addArc(scaledNormal, true);
617 SkScalar z = fTransformedHeightFunc(p);
618 fRadius = this->offset(z);
619 fUmbraColor = this->umbraColor(z);
Jim Van Verthda965502017-04-11 15:29:14 -0400620 this->addEdge(p, normal);
Jim Van Verthbce74962017-01-25 09:39:46 -0500621 }
622}
623
Jim Van Verthefe3ded2017-01-30 13:11:45 -0500624void SkAmbientShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVector& nextNormal) {
Jim Van Verth76387852017-05-16 16:55:16 -0400625 // We compute the inset in two stages: first we inset by half the current normal,
626 // then on the next addEdge() we add half of the next normal to get an average of the two
627 SkVector insetNormal = nextNormal;
628 insetNormal *= 0.5f*kInsetFactor;
629
630 // Adding the other half of the average for the previous edge
631 fPositions[fPrevUmbraIndex] += insetNormal;
632
633 SkPoint umbraPoint = nextPoint + insetNormal;
634 SkVector outsetNormal = nextNormal;
635 outsetNormal *= fRadius;
636 SkPoint penumbraPoint = nextPoint + outsetNormal;
637
638 // For split edges, we're adding an average of two averages, so we multiply by another half
639 if (fSplitPreviousEdge) {
640 insetNormal *= 0.5f;
641 fPositions[fPrevUmbraIndex - 2] += insetNormal;
642 }
643
644 // 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 -0400645 if (fColors[fPrevUmbraIndex] != fUmbraColor &&
646 nextPoint.distanceToSqd(fPositions[fPrevUmbraIndex]) > kMaxEdgeLenSqr) {
Jim Van Verth76387852017-05-16 16:55:16 -0400647
648 // This is lacking 1/4 of the next inset -- we'll add it the next time we call addEdge()
649 SkPoint centerPoint = fPositions[fPrevUmbraIndex] + umbraPoint;
Jim Van Verthda965502017-04-11 15:29:14 -0400650 centerPoint *= 0.5f;
651 *fPositions.push() = centerPoint;
652 *fColors.push() = SkPMLerp(fUmbraColor, fColors[fPrevUmbraIndex], 128);
Jim Van Verth76387852017-05-16 16:55:16 -0400653 centerPoint = fPositions[fPositions.count()-2] + penumbraPoint;
654 centerPoint *= 0.5f;
655 *fPositions.push() = centerPoint;
Jim Van Verthda965502017-04-11 15:29:14 -0400656 *fColors.push() = fPenumbraColor;
657
658 // set triangularization to get best interpolation of color
659 if (fColors[fPrevUmbraIndex] > fColors[fPositions.count() - 2]) {
660 *fIndices.push() = fPrevUmbraIndex;
661 *fIndices.push() = fPositions.count() - 3;
662 *fIndices.push() = fPositions.count() - 2;
663
664 *fIndices.push() = fPositions.count() - 3;
665 *fIndices.push() = fPositions.count() - 1;
666 *fIndices.push() = fPositions.count() - 2;
667 } else {
668 *fIndices.push() = fPrevUmbraIndex;
669 *fIndices.push() = fPositions.count() - 2;
670 *fIndices.push() = fPositions.count() - 1;
671
672 *fIndices.push() = fPrevUmbraIndex;
673 *fIndices.push() = fPositions.count() - 1;
674 *fIndices.push() = fPositions.count() - 3;
675 }
676
Jim Van Verth1c4c1142017-05-11 10:23:53 -0400677 // if transparent, add point to first one in array and add to center fan
678 if (fTransparent) {
679 fPositions[0] += centerPoint;
680 ++fCentroidCount;
681
682 *fIndices.push() = 0;
683 *fIndices.push() = fPrevUmbraIndex;
684 *fIndices.push() = fPositions.count() - 2;
685 }
686
Jim Van Verth76387852017-05-16 16:55:16 -0400687 fSplitPreviousEdge = true;
688 if (fPrevUmbraIndex == fFirstVertexIndex) {
689 fSplitFirstEdge = true;
690 }
Jim Van Verthda965502017-04-11 15:29:14 -0400691 fPrevUmbraIndex = fPositions.count() - 2;
Jim Van Verth76387852017-05-16 16:55:16 -0400692 } else {
693 fSplitPreviousEdge = false;
Jim Van Verthda965502017-04-11 15:29:14 -0400694 }
695
Jim Van Verthbce74962017-01-25 09:39:46 -0500696 // add next quad
Jim Van Verth76387852017-05-16 16:55:16 -0400697 *fPositions.push() = umbraPoint;
Jim Van Verthbce74962017-01-25 09:39:46 -0500698 *fColors.push() = fUmbraColor;
Jim Van Verth76387852017-05-16 16:55:16 -0400699 *fPositions.push() = penumbraPoint;
Jim Van Verthbce74962017-01-25 09:39:46 -0500700 *fColors.push() = fPenumbraColor;
701
Jim Van Verthda965502017-04-11 15:29:14 -0400702 // set triangularization to get best interpolation of color
703 if (fColors[fPrevUmbraIndex] > fColors[fPositions.count() - 2]) {
704 *fIndices.push() = fPrevUmbraIndex;
705 *fIndices.push() = fPositions.count() - 3;
706 *fIndices.push() = fPositions.count() - 2;
Jim Van Verthbce74962017-01-25 09:39:46 -0500707
Jim Van Verthda965502017-04-11 15:29:14 -0400708 *fIndices.push() = fPositions.count() - 3;
709 *fIndices.push() = fPositions.count() - 1;
710 *fIndices.push() = fPositions.count() - 2;
711 } else {
712 *fIndices.push() = fPrevUmbraIndex;
713 *fIndices.push() = fPositions.count() - 2;
714 *fIndices.push() = fPositions.count() - 1;
715
716 *fIndices.push() = fPrevUmbraIndex;
717 *fIndices.push() = fPositions.count() - 1;
718 *fIndices.push() = fPositions.count() - 3;
719 }
Jim Van Verthbce74962017-01-25 09:39:46 -0500720
721 // if transparent, add point to first one in array and add to center fan
722 if (fTransparent) {
723 fPositions[0] += nextPoint;
724 ++fCentroidCount;
725
726 *fIndices.push() = 0;
Brian Salomon66085ed2017-02-02 15:39:34 -0500727 *fIndices.push() = fPrevUmbraIndex;
Jim Van Verthbce74962017-01-25 09:39:46 -0500728 *fIndices.push() = fPositions.count() - 2;
729 }
730
Brian Salomon66085ed2017-02-02 15:39:34 -0500731 fPrevUmbraIndex = fPositions.count() - 2;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500732 fPrevPoint = nextPoint;
Jim Van Verth76387852017-05-16 16:55:16 -0400733 fPrevOutset = outsetNormal;
Jim Van Verthbce74962017-01-25 09:39:46 -0500734}
Jim Van Verth91af7272017-01-27 14:15:54 -0500735
736///////////////////////////////////////////////////////////////////////////////////////////////////
737
Brian Salomonaff27a22017-02-06 15:47:44 -0500738class SkSpotShadowTessellator : public SkBaseShadowTessellator {
Brian Salomon958fbc42017-01-30 17:01:28 -0500739public:
Jim Van Vertha84898d2017-02-06 13:38:23 -0500740 SkSpotShadowTessellator(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -0400741 const SkPoint3& zPlaneParams, const SkPoint3& lightPos,
Jim Van Verth060d9822017-05-04 09:58:17 -0400742 SkScalar lightRadius, bool transparent);
Brian Salomon958fbc42017-01-30 17:01:28 -0500743
Brian Salomon958fbc42017-01-30 17:01:28 -0500744private:
Brian Salomonab664fa2017-03-24 16:07:20 +0000745 void computeClipAndPathPolygons(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthda965502017-04-11 15:29:14 -0400746 const SkMatrix& shadowTransform);
Brian Salomonab664fa2017-03-24 16:07:20 +0000747 void computeClipVectorsAndTestCentroid();
Brian Salomon66085ed2017-02-02 15:39:34 -0500748 bool clipUmbraPoint(const SkPoint& umbraPoint, const SkPoint& centroid, SkPoint* clipPoint);
Brian Salomonab664fa2017-03-24 16:07:20 +0000749 int getClosestUmbraPoint(const SkPoint& point);
Brian Salomon958fbc42017-01-30 17:01:28 -0500750
Jim Van Vertha84898d2017-02-06 13:38:23 -0500751 void handleLine(const SkPoint& p) override;
Brian Salomonab664fa2017-03-24 16:07:20 +0000752 void handlePolyPoint(const SkPoint& p);
Brian Salomon958fbc42017-01-30 17:01:28 -0500753
754 void mapPoints(SkScalar scale, const SkVector& xlate, SkPoint* pts, int count);
Brian Salomonab664fa2017-03-24 16:07:20 +0000755 bool addInnerPoint(const SkPoint& pathPoint);
Jim Van Verthda965502017-04-11 15:29:14 -0400756 void addEdge(const SkVector& nextPoint, const SkVector& nextNormal);
757
758 SkScalar offset(SkScalar z) {
759 float zRatio = SkTPin(z / (fLightZ - z), 0.0f, 0.95f);
760 return fLightRadius*zRatio;
761 }
762
763 SkScalar fLightZ;
764 SkScalar fLightRadius;
765 SkScalar fOffsetAdjust;
Brian Salomon958fbc42017-01-30 17:01:28 -0500766
Brian Salomon958fbc42017-01-30 17:01:28 -0500767 SkTDArray<SkPoint> fClipPolygon;
Brian Salomon66085ed2017-02-02 15:39:34 -0500768 SkTDArray<SkVector> fClipVectors;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500769 SkPoint fCentroid;
Brian Salomonab664fa2017-03-24 16:07:20 +0000770 SkScalar fArea;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500771
Brian Salomonab664fa2017-03-24 16:07:20 +0000772 SkTDArray<SkPoint> fPathPolygon;
773 SkTDArray<SkPoint> fUmbraPolygon;
774 int fCurrClipPoint;
775 int fCurrUmbraPoint;
Brian Salomon66085ed2017-02-02 15:39:34 -0500776 bool fPrevUmbraOutside;
777 bool fFirstUmbraOutside;
Jim Van Vertha84898d2017-02-06 13:38:23 -0500778 bool fValidUmbra;
Brian Salomon958fbc42017-01-30 17:01:28 -0500779
Brian Salomonaff27a22017-02-06 15:47:44 -0500780 typedef SkBaseShadowTessellator INHERITED;
Brian Salomon958fbc42017-01-30 17:01:28 -0500781};
782
Jim Van Vertha84898d2017-02-06 13:38:23 -0500783SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -0400784 const SkPoint3& zPlaneParams,
Jim Van Verthb4366552017-03-27 14:25:29 -0400785 const SkPoint3& lightPos, SkScalar lightRadius,
Jim Van Verth060d9822017-05-04 09:58:17 -0400786 bool transparent)
Jim Van Verthe308a122017-05-08 14:19:30 -0400787 : INHERITED(zPlaneParams, transparent)
Jim Van Verthda965502017-04-11 15:29:14 -0400788 , fLightZ(lightPos.fZ)
789 , fLightRadius(lightRadius)
790 , fOffsetAdjust(0)
791 , fCurrClipPoint(0)
792 , fPrevUmbraOutside(false)
793 , fFirstUmbraOutside(false)
794 , fValidUmbra(true) {
795
796 // make sure we're not below the canvas plane
797 if (this->setZOffset(path.getBounds(), ctm.hasPerspective())) {
798 // Adjust light height and radius
799 fLightRadius *= (fLightZ + fZOffset) / fLightZ;
800 fLightZ += fZOffset;
801 }
Jim Van Verthb4366552017-03-27 14:25:29 -0400802
803 // Set radius and colors
Jim Van Verthb4366552017-03-27 14:25:29 -0400804 SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY());
Jim Van Verthe308a122017-05-08 14:19:30 -0400805 SkScalar occluderHeight = this->heightFunc(center.fX, center.fY) + fZOffset;
Jim Van Verthda965502017-04-11 15:29:14 -0400806 float zRatio = SkTPin(occluderHeight / (fLightZ - occluderHeight), 0.0f, 0.95f);
Jim Van Verthb4366552017-03-27 14:25:29 -0400807 SkScalar radius = lightRadius * zRatio;
808 fRadius = radius;
Jim Van Verth060d9822017-05-04 09:58:17 -0400809 fUmbraColor = SkColorSetARGB(255, 0, 0, 0);
810 fPenumbraColor = SkColorSetARGB(0, 0, 0, 0);
Jim Van Verthb4366552017-03-27 14:25:29 -0400811
812 // Compute the scale and translation for the spot shadow.
Jim Van Verthda965502017-04-11 15:29:14 -0400813 SkMatrix shadowTransform;
814 if (!ctm.hasPerspective()) {
815 SkScalar scale = fLightZ / (fLightZ - occluderHeight);
816 SkVector translate = SkVector::Make(-zRatio * lightPos.fX, -zRatio * lightPos.fY);
817 shadowTransform.setScaleTranslate(scale, scale, translate.fX, translate.fY);
818 } else {
819 // For perspective, we have a scale, a z-shear, and another projective divide --
820 // this varies at each point so we can't use an affine transform.
821 // We'll just apply this to each generated point in turn.
822 shadowTransform.reset();
823 // Also can't cull the center (for now).
824 fTransparent = true;
825 }
826 SkMatrix fullTransform = SkMatrix::Concat(shadowTransform, ctm);
827
828 // Set up our reverse mapping
829 this->setTransformedHeightFunc(fullTransform);
Jim Van Verthb4366552017-03-27 14:25:29 -0400830
Brian Salomonab664fa2017-03-24 16:07:20 +0000831 // TODO: calculate these reserves better
Brian Salomon66085ed2017-02-02 15:39:34 -0500832 // Penumbra ring: 3*numPts
833 // Umbra ring: numPts
Jim Van Verth91af7272017-01-27 14:15:54 -0500834 // Inner ring: numPts
Brian Salomon66085ed2017-02-02 15:39:34 -0500835 fPositions.setReserve(5 * path.countPoints());
836 fColors.setReserve(5 * path.countPoints());
837 // Penumbra ring: 12*numPts
838 // Umbra ring: 3*numPts
839 fIndices.setReserve(15 * path.countPoints());
Brian Salomone7c85c42017-03-24 16:00:35 +0000840 fClipPolygon.setReserve(path.countPoints());
Brian Salomonab664fa2017-03-24 16:07:20 +0000841
842 // compute rough clip bounds for umbra, plus offset polygon, plus centroid
Jim Van Verthda965502017-04-11 15:29:14 -0400843 this->computeClipAndPathPolygons(path, ctm, shadowTransform);
Brian Salomonab664fa2017-03-24 16:07:20 +0000844 if (fClipPolygon.count() < 3 || fPathPolygon.count() < 3) {
Brian Salomon66085ed2017-02-02 15:39:34 -0500845 return;
846 }
Brian Salomon66085ed2017-02-02 15:39:34 -0500847
Brian Salomonab664fa2017-03-24 16:07:20 +0000848 // check to see if umbra collapses
849 SkScalar minDistSq = fCentroid.distanceToLineSegmentBetweenSqd(fPathPolygon[0],
850 fPathPolygon[1]);
Jim Van Verthda965502017-04-11 15:29:14 -0400851 SkRect bounds;
852 bounds.setBounds(&fPathPolygon[0], fPathPolygon.count());
Brian Salomonab664fa2017-03-24 16:07:20 +0000853 for (int i = 1; i < fPathPolygon.count(); ++i) {
854 int j = i + 1;
855 if (i == fPathPolygon.count() - 1) {
856 j = 0;
857 }
858 SkPoint currPoint = fPathPolygon[i];
859 SkPoint nextPoint = fPathPolygon[j];
860 SkScalar distSq = fCentroid.distanceToLineSegmentBetweenSqd(currPoint, nextPoint);
861 if (distSq < minDistSq) {
862 minDistSq = distSq;
863 }
864 }
865 static constexpr auto kTolerance = 1.0e-2f;
866 if (minDistSq < (radius + kTolerance)*(radius + kTolerance)) {
867 // if the umbra would collapse, we back off a bit on inner blur and adjust the alpha
868 SkScalar newRadius = SkScalarSqrt(minDistSq) - kTolerance;
Jim Van Verthda965502017-04-11 15:29:14 -0400869 fOffsetAdjust = newRadius - radius;
Jim Van Verthb6069df2017-04-28 11:00:35 -0400870 SkScalar ratio = 128 * (newRadius + radius) / radius;
Brian Salomonab664fa2017-03-24 16:07:20 +0000871 // they aren't PMColors, but the interpolation algorithm is the same
872 fUmbraColor = SkPMLerp(fUmbraColor, fPenumbraColor, (unsigned)ratio);
873 radius = newRadius;
874 }
Jim Van Verth91af7272017-01-27 14:15:54 -0500875
Brian Salomonab664fa2017-03-24 16:07:20 +0000876 // compute vectors for clip tests
877 this->computeClipVectorsAndTestCentroid();
878
879 // generate inner ring
Jim Van Verthda965502017-04-11 15:29:14 -0400880 if (!SkInsetConvexPolygon(&fPathPolygon[0], fPathPolygon.count(), radius,
881 &fUmbraPolygon)) {
Brian Salomonab664fa2017-03-24 16:07:20 +0000882 // this shouldn't happen, but just in case we'll inset using the centroid
883 fValidUmbra = false;
884 }
885
886 // walk around the path polygon, generate outer ring and connect to inner ring
Brian Salomon66085ed2017-02-02 15:39:34 -0500887 if (fTransparent) {
888 *fPositions.push() = fCentroid;
889 *fColors.push() = fUmbraColor;
890 }
Brian Salomonab664fa2017-03-24 16:07:20 +0000891 fCurrUmbraPoint = 0;
892 for (int i = 0; i < fPathPolygon.count(); ++i) {
893 this->handlePolyPoint(fPathPolygon[i]);
Jim Van Verth91af7272017-01-27 14:15:54 -0500894 }
895
Brian Salomon0dda9cb2017-02-03 10:33:25 -0500896 if (!this->indexCount()) {
897 return;
898 }
899
Brian Salomonab664fa2017-03-24 16:07:20 +0000900 // finish up the final verts
Jim Van Verth91af7272017-01-27 14:15:54 -0500901 SkVector normal;
Jim Van Verthda965502017-04-11 15:29:14 -0400902 if (compute_normal(fPrevPoint, fFirstPoint, fDirection, &normal)) {
903 normal *= fRadius;
904 this->addArc(normal, true);
Jim Van Verth91af7272017-01-27 14:15:54 -0500905
Brian Salomon66085ed2017-02-02 15:39:34 -0500906 // add to center fan
907 if (fTransparent) {
908 *fIndices.push() = 0;
909 *fIndices.push() = fPrevUmbraIndex;
Jim Van Verth76387852017-05-16 16:55:16 -0400910 *fIndices.push() = fFirstVertexIndex;
Brian Salomon66085ed2017-02-02 15:39:34 -0500911 // or to clip ring
912 } else {
913 if (fFirstUmbraOutside) {
914 *fIndices.push() = fPrevUmbraIndex;
Jim Van Verth76387852017-05-16 16:55:16 -0400915 *fIndices.push() = fFirstVertexIndex;
916 *fIndices.push() = fFirstVertexIndex + 1;
Brian Salomon66085ed2017-02-02 15:39:34 -0500917 if (fPrevUmbraOutside) {
918 // fill out quad
919 *fIndices.push() = fPrevUmbraIndex;
Jim Van Verth76387852017-05-16 16:55:16 -0400920 *fIndices.push() = fFirstVertexIndex + 1;
Brian Salomon66085ed2017-02-02 15:39:34 -0500921 *fIndices.push() = fPrevUmbraIndex + 1;
922 }
923 } else if (fPrevUmbraOutside) {
924 // add tri
925 *fIndices.push() = fPrevUmbraIndex;
Jim Van Verth76387852017-05-16 16:55:16 -0400926 *fIndices.push() = fFirstVertexIndex;
Brian Salomon66085ed2017-02-02 15:39:34 -0500927 *fIndices.push() = fPrevUmbraIndex + 1;
928 }
929 }
930
Jim Van Verth91af7272017-01-27 14:15:54 -0500931 // add final edge
932 *fPositions.push() = fFirstPoint + normal;
933 *fColors.push() = fPenumbraColor;
934
Brian Salomon66085ed2017-02-02 15:39:34 -0500935 *fIndices.push() = fPrevUmbraIndex;
Jim Van Verth91af7272017-01-27 14:15:54 -0500936 *fIndices.push() = fPositions.count() - 2;
Jim Van Verth76387852017-05-16 16:55:16 -0400937 *fIndices.push() = fFirstVertexIndex;
Jim Van Verth91af7272017-01-27 14:15:54 -0500938
939 *fIndices.push() = fPositions.count() - 2;
940 *fIndices.push() = fPositions.count() - 1;
Jim Van Verth76387852017-05-16 16:55:16 -0400941 *fIndices.push() = fFirstVertexIndex;
Jim Van Verthda965502017-04-11 15:29:14 -0400942
Jim Van Verth76387852017-05-16 16:55:16 -0400943 fPrevOutset = normal;
Jim Van Verth91af7272017-01-27 14:15:54 -0500944 }
945
946 // final fan
947 if (fPositions.count() >= 3) {
Jim Van Verth76387852017-05-16 16:55:16 -0400948 fPrevUmbraIndex = fFirstVertexIndex;
Jim Van Verth91af7272017-01-27 14:15:54 -0500949 fPrevPoint = fFirstPoint;
Jim Van Verth76387852017-05-16 16:55:16 -0400950 if (this->addArc(fFirstOutset, false)) {
951 *fIndices.push() = fFirstVertexIndex;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400952 *fIndices.push() = fPositions.count() - 1;
953 if (fFirstUmbraOutside) {
Jim Van Verth76387852017-05-16 16:55:16 -0400954 *fIndices.push() = fFirstVertexIndex + 2;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400955 } else {
Jim Van Verth76387852017-05-16 16:55:16 -0400956 *fIndices.push() = fFirstVertexIndex + 1;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400957 }
Brian Salomon66085ed2017-02-02 15:39:34 -0500958 } else {
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400959 // no arc added, fix up by setting first penumbra point position to last one
960 if (fFirstUmbraOutside) {
Jim Van Verth76387852017-05-16 16:55:16 -0400961 fPositions[fFirstVertexIndex + 2] = fPositions[fPositions.count() - 1];
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400962 } else {
Jim Van Verth76387852017-05-16 16:55:16 -0400963 fPositions[fFirstVertexIndex + 1] = fPositions[fPositions.count() - 1];
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -0400964 }
Brian Salomon66085ed2017-02-02 15:39:34 -0500965 }
Jim Van Verth91af7272017-01-27 14:15:54 -0500966 }
Jim Van Verthda965502017-04-11 15:29:14 -0400967
968 if (ctm.hasPerspective()) {
969 for (int i = 0; i < fPositions.count(); ++i) {
970 SkScalar pathZ = fTransformedHeightFunc(fPositions[i]);
971 SkScalar factor = SkScalarInvert(fLightZ - pathZ);
972 fPositions[i].fX = (fPositions[i].fX*fLightZ - lightPos.fX*pathZ)*factor;
973 fPositions[i].fY = (fPositions[i].fY*fLightZ - lightPos.fY*pathZ)*factor;
974 }
975#ifdef DRAW_CENTROID
976 SkScalar pathZ = fTransformedHeightFunc(fCentroid);
977 SkScalar factor = SkScalarInvert(fLightZ - pathZ);
978 fCentroid.fX = (fCentroid.fX*fLightZ - lightPos.fX*pathZ)*factor;
979 fCentroid.fY = (fCentroid.fY*fLightZ - lightPos.fY*pathZ)*factor;
980#endif
981 }
982#ifdef DRAW_CENTROID
983 *fPositions.push() = fCentroid + SkVector::Make(-2, -2);
984 *fColors.push() = SkColorSetARGB(255, 0, 255, 255);
985 *fPositions.push() = fCentroid + SkVector::Make(2, -2);
986 *fColors.push() = SkColorSetARGB(255, 0, 255, 255);
987 *fPositions.push() = fCentroid + SkVector::Make(-2, 2);
988 *fColors.push() = SkColorSetARGB(255, 0, 255, 255);
989 *fPositions.push() = fCentroid + SkVector::Make(2, 2);
990 *fColors.push() = SkColorSetARGB(255, 0, 255, 255);
991
992 *fIndices.push() = fPositions.count() - 4;
993 *fIndices.push() = fPositions.count() - 2;
994 *fIndices.push() = fPositions.count() - 1;
995
996 *fIndices.push() = fPositions.count() - 4;
997 *fIndices.push() = fPositions.count() - 1;
998 *fIndices.push() = fPositions.count() - 3;
999#endif
1000
Brian Salomon0dda9cb2017-02-03 10:33:25 -05001001 fSucceeded = true;
Jim Van Verth91af7272017-01-27 14:15:54 -05001002}
1003
Brian Salomonab664fa2017-03-24 16:07:20 +00001004void SkSpotShadowTessellator::computeClipAndPathPolygons(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthda965502017-04-11 15:29:14 -04001005 const SkMatrix& shadowTransform) {
Brian Salomonab664fa2017-03-24 16:07:20 +00001006
1007 fPathPolygon.setReserve(path.countPoints());
1008
1009 // Walk around the path and compute clip polygon and path polygon.
1010 // Will also accumulate sum of areas for centroid.
1011 // For Bezier curves, we compute additional interior points on curve.
Jim Van Verth91af7272017-01-27 14:15:54 -05001012 SkPath::Iter iter(path, true);
1013 SkPoint pts[4];
1014 SkPath::Verb verb;
1015
Jim Van Verth91af7272017-01-27 14:15:54 -05001016 fClipPolygon.reset();
1017
Brian Salomonab664fa2017-03-24 16:07:20 +00001018 // init centroid
1019 fCentroid = SkPoint::Make(0, 0);
1020 fArea = 0;
1021
Brian Salomon66085ed2017-02-02 15:39:34 -05001022 // coefficients to compute cubic Bezier at t = 5/16
Brian Salomonab664fa2017-03-24 16:07:20 +00001023 static constexpr SkScalar kA = 0.32495117187f;
1024 static constexpr SkScalar kB = 0.44311523437f;
1025 static constexpr SkScalar kC = 0.20141601562f;
1026 static constexpr SkScalar kD = 0.03051757812f;
Brian Salomon66085ed2017-02-02 15:39:34 -05001027
1028 SkPoint curvePoint;
1029 SkScalar w;
Jim Van Verth91af7272017-01-27 14:15:54 -05001030 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1031 switch (verb) {
Jim Van Verth91af7272017-01-27 14:15:54 -05001032 case SkPath::kLine_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -05001033 ctm.mapPoints(&pts[1], 1);
Jim Van Verth91af7272017-01-27 14:15:54 -05001034 *fClipPolygon.push() = pts[1];
Brian Salomonab664fa2017-03-24 16:07:20 +00001035 this->INHERITED::handleLine(shadowTransform, &pts[1]);
Jim Van Verth91af7272017-01-27 14:15:54 -05001036 break;
1037 case SkPath::kQuad_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -05001038 ctm.mapPoints(pts, 3);
Brian Salomon66085ed2017-02-02 15:39:34 -05001039 // point at t = 1/2
1040 curvePoint.fX = 0.25f*pts[0].fX + 0.5f*pts[1].fX + 0.25f*pts[2].fX;
1041 curvePoint.fY = 0.25f*pts[0].fY + 0.5f*pts[1].fY + 0.25f*pts[2].fY;
1042 *fClipPolygon.push() = curvePoint;
Brian Salomon66085ed2017-02-02 15:39:34 -05001043 *fClipPolygon.push() = pts[2];
Brian Salomonab664fa2017-03-24 16:07:20 +00001044 this->handleQuad(shadowTransform, pts);
Jim Van Verth91af7272017-01-27 14:15:54 -05001045 break;
1046 case SkPath::kConic_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -05001047 ctm.mapPoints(pts, 3);
Brian Salomon66085ed2017-02-02 15:39:34 -05001048 w = iter.conicWeight();
Jim Van Vertha84898d2017-02-06 13:38:23 -05001049 // point at t = 1/2
Brian Salomon66085ed2017-02-02 15:39:34 -05001050 curvePoint.fX = 0.25f*pts[0].fX + w*0.5f*pts[1].fX + 0.25f*pts[2].fX;
1051 curvePoint.fY = 0.25f*pts[0].fY + w*0.5f*pts[1].fY + 0.25f*pts[2].fY;
1052 curvePoint *= SkScalarInvert(0.5f + 0.5f*w);
1053 *fClipPolygon.push() = curvePoint;
Brian Salomon66085ed2017-02-02 15:39:34 -05001054 *fClipPolygon.push() = pts[2];
Brian Salomonab664fa2017-03-24 16:07:20 +00001055 this->handleConic(shadowTransform, pts, w);
Jim Van Verth91af7272017-01-27 14:15:54 -05001056 break;
1057 case SkPath::kCubic_Verb:
Jim Van Vertha84898d2017-02-06 13:38:23 -05001058 ctm.mapPoints(pts, 4);
Brian Salomon66085ed2017-02-02 15:39:34 -05001059 // point at t = 5/16
1060 curvePoint.fX = kA*pts[0].fX + kB*pts[1].fX + kC*pts[2].fX + kD*pts[3].fX;
1061 curvePoint.fY = kA*pts[0].fY + kB*pts[1].fY + kC*pts[2].fY + kD*pts[3].fY;
1062 *fClipPolygon.push() = curvePoint;
Brian Salomon66085ed2017-02-02 15:39:34 -05001063 // point at t = 11/16
1064 curvePoint.fX = kD*pts[0].fX + kC*pts[1].fX + kB*pts[2].fX + kA*pts[3].fX;
1065 curvePoint.fY = kD*pts[0].fY + kC*pts[1].fY + kB*pts[2].fY + kA*pts[3].fY;
1066 *fClipPolygon.push() = curvePoint;
Brian Salomon66085ed2017-02-02 15:39:34 -05001067 *fClipPolygon.push() = pts[3];
Brian Salomonab664fa2017-03-24 16:07:20 +00001068 this->handleCubic(shadowTransform, pts);
Jim Van Verth91af7272017-01-27 14:15:54 -05001069 break;
Brian Salomonab664fa2017-03-24 16:07:20 +00001070 case SkPath::kMove_Verb:
Jim Van Verth91af7272017-01-27 14:15:54 -05001071 case SkPath::kClose_Verb:
Brian Salomonab664fa2017-03-24 16:07:20 +00001072 case SkPath::kDone_Verb:
Jim Van Verth91af7272017-01-27 14:15:54 -05001073 break;
1074 default:
1075 SkDEBUGFAIL("unknown verb");
1076 }
1077 }
1078
Brian Salomonab664fa2017-03-24 16:07:20 +00001079 // finish centroid
1080 if (fPathPolygon.count() > 0) {
1081 SkPoint currPoint = fPathPolygon[fPathPolygon.count() - 1];
1082 SkPoint nextPoint = fPathPolygon[0];
1083 SkScalar quadArea = currPoint.cross(nextPoint);
1084 fCentroid.fX += (currPoint.fX + nextPoint.fX) * quadArea;
1085 fCentroid.fY += (currPoint.fY + nextPoint.fY) * quadArea;
1086 fArea += quadArea;
1087 fCentroid *= SK_Scalar1 / (3 * fArea);
1088 }
1089
1090 fCurrClipPoint = fClipPolygon.count() - 1;
Brian Salomon66085ed2017-02-02 15:39:34 -05001091}
1092
Brian Salomonab664fa2017-03-24 16:07:20 +00001093void SkSpotShadowTessellator::computeClipVectorsAndTestCentroid() {
Brian Salomon66085ed2017-02-02 15:39:34 -05001094 SkASSERT(fClipPolygon.count() >= 3);
Brian Salomon66085ed2017-02-02 15:39:34 -05001095
Brian Salomonab664fa2017-03-24 16:07:20 +00001096 // init clip vectors
Brian Salomon66085ed2017-02-02 15:39:34 -05001097 SkVector v0 = fClipPolygon[1] - fClipPolygon[0];
1098 *fClipVectors.push() = v0;
Brian Salomon66085ed2017-02-02 15:39:34 -05001099
1100 // init centroid check
1101 bool hiddenCentroid = true;
Brian Salomonab664fa2017-03-24 16:07:20 +00001102 SkVector v1 = fCentroid - fClipPolygon[0];
Brian Salomon66085ed2017-02-02 15:39:34 -05001103 SkScalar initCross = v0.cross(v1);
1104
1105 for (int p = 1; p < fClipPolygon.count(); ++p) {
Brian Salomonab664fa2017-03-24 16:07:20 +00001106 // add to clip vectors
Brian Salomon66085ed2017-02-02 15:39:34 -05001107 v0 = fClipPolygon[(p + 1) % fClipPolygon.count()] - fClipPolygon[p];
1108 *fClipVectors.push() = v0;
Brian Salomon66085ed2017-02-02 15:39:34 -05001109 // Determine if transformed centroid is inside clipPolygon.
Brian Salomonab664fa2017-03-24 16:07:20 +00001110 v1 = fCentroid - fClipPolygon[p];
Brian Salomon66085ed2017-02-02 15:39:34 -05001111 if (initCross*v0.cross(v1) <= 0) {
1112 hiddenCentroid = false;
1113 }
1114 }
1115 SkASSERT(fClipVectors.count() == fClipPolygon.count());
1116
Brian Salomonab664fa2017-03-24 16:07:20 +00001117 fTransparent = fTransparent || !hiddenCentroid;
Brian Salomon66085ed2017-02-02 15:39:34 -05001118}
1119
1120bool SkSpotShadowTessellator::clipUmbraPoint(const SkPoint& umbraPoint, const SkPoint& centroid,
1121 SkPoint* clipPoint) {
1122 SkVector segmentVector = centroid - umbraPoint;
1123
Brian Salomonab664fa2017-03-24 16:07:20 +00001124 int startClipPoint = fCurrClipPoint;
Brian Salomon66085ed2017-02-02 15:39:34 -05001125 do {
Brian Salomonab664fa2017-03-24 16:07:20 +00001126 SkVector dp = umbraPoint - fClipPolygon[fCurrClipPoint];
1127 SkScalar denom = fClipVectors[fCurrClipPoint].cross(segmentVector);
Brian Salomon66085ed2017-02-02 15:39:34 -05001128 SkScalar t_num = dp.cross(segmentVector);
1129 // if line segments are nearly parallel
1130 if (SkScalarNearlyZero(denom)) {
1131 // and collinear
1132 if (SkScalarNearlyZero(t_num)) {
1133 return false;
1134 }
1135 // otherwise are separate, will try the next poly segment
1136 // else if crossing lies within poly segment
1137 } else if (t_num >= 0 && t_num <= denom) {
Brian Salomonab664fa2017-03-24 16:07:20 +00001138 SkScalar s_num = dp.cross(fClipVectors[fCurrClipPoint]);
Brian Salomon66085ed2017-02-02 15:39:34 -05001139 // if umbra point is inside the clip polygon
Jim Van Verthda965502017-04-11 15:29:14 -04001140 if (s_num >= 0 && s_num <= denom) {
Brian Salomon66085ed2017-02-02 15:39:34 -05001141 segmentVector *= s_num/denom;
1142 *clipPoint = umbraPoint + segmentVector;
1143 return true;
1144 }
1145 }
Brian Salomonab664fa2017-03-24 16:07:20 +00001146 fCurrClipPoint = (fCurrClipPoint + 1) % fClipPolygon.count();
1147 } while (fCurrClipPoint != startClipPoint);
Brian Salomon66085ed2017-02-02 15:39:34 -05001148
1149 return false;
Jim Van Verth91af7272017-01-27 14:15:54 -05001150}
1151
Brian Salomonab664fa2017-03-24 16:07:20 +00001152int SkSpotShadowTessellator::getClosestUmbraPoint(const SkPoint& p) {
1153 SkScalar minDistance = p.distanceToSqd(fUmbraPolygon[fCurrUmbraPoint]);
1154 int index = fCurrUmbraPoint;
1155 int dir = 1;
1156 int next = (index + dir) % fUmbraPolygon.count();
1157
1158 // init travel direction
1159 SkScalar distance = p.distanceToSqd(fUmbraPolygon[next]);
1160 if (distance < minDistance) {
1161 index = next;
1162 minDistance = distance;
1163 } else {
1164 dir = fUmbraPolygon.count()-1;
1165 }
1166
1167 // iterate until we find a point that increases the distance
1168 next = (index + dir) % fUmbraPolygon.count();
1169 distance = p.distanceToSqd(fUmbraPolygon[next]);
1170 while (distance < minDistance) {
1171 index = next;
1172 minDistance = distance;
1173 next = (index + dir) % fUmbraPolygon.count();
1174 distance = p.distanceToSqd(fUmbraPolygon[next]);
1175 }
1176
1177 fCurrUmbraPoint = index;
1178 return index;
1179}
1180
Jim Van Verthefe3ded2017-01-30 13:11:45 -05001181void SkSpotShadowTessellator::mapPoints(SkScalar scale, const SkVector& xlate,
Jim Van Verth91af7272017-01-27 14:15:54 -05001182 SkPoint* pts, int count) {
1183 // TODO: vectorize
1184 for (int i = 0; i < count; ++i) {
1185 pts[i] *= scale;
1186 pts[i] += xlate;
1187 }
1188}
1189
Brian Salomonab664fa2017-03-24 16:07:20 +00001190static bool duplicate_pt(const SkPoint& p0, const SkPoint& p1) {
1191 static constexpr SkScalar kClose = (SK_Scalar1 / 16);
1192 static constexpr SkScalar kCloseSqd = kClose*kClose;
1193
1194 SkScalar distSq = p0.distanceToSqd(p1);
1195 return distSq < kCloseSqd;
1196}
1197
1198static bool is_collinear(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
1199 SkVector v0 = p1 - p0;
1200 SkVector v1 = p2 - p0;
1201 return (SkScalarNearlyZero(v0.cross(v1)));
1202}
1203
Jim Van Verthefe3ded2017-01-30 13:11:45 -05001204void SkSpotShadowTessellator::handleLine(const SkPoint& p) {
Brian Salomonab664fa2017-03-24 16:07:20 +00001205 // remove coincident points and add to centroid
1206 if (fPathPolygon.count() > 0) {
1207 const SkPoint& lastPoint = fPathPolygon[fPathPolygon.count() - 1];
1208 if (duplicate_pt(p, lastPoint)) {
1209 return;
1210 }
1211 SkScalar quadArea = lastPoint.cross(p);
1212 fCentroid.fX += (p.fX + lastPoint.fX) * quadArea;
1213 fCentroid.fY += (p.fY + lastPoint.fY) * quadArea;
1214 fArea += quadArea;
1215 }
1216
1217 // try to remove collinear points
1218 if (fPathPolygon.count() > 1 && is_collinear(fPathPolygon[fPathPolygon.count()-2],
1219 fPathPolygon[fPathPolygon.count()-1],
1220 p)) {
1221 fPathPolygon[fPathPolygon.count() - 1] = p;
1222 } else {
1223 *fPathPolygon.push() = p;
1224 }
1225}
1226
1227void SkSpotShadowTessellator::handlePolyPoint(const SkPoint& p) {
Jim Van Verth91af7272017-01-27 14:15:54 -05001228 if (fInitPoints.count() < 2) {
1229 *fInitPoints.push() = p;
1230 return;
1231 }
1232
1233 if (fInitPoints.count() == 2) {
1234 // determine if cw or ccw
1235 SkVector v0 = fInitPoints[1] - fInitPoints[0];
1236 SkVector v1 = p - fInitPoints[0];
Brian Salomonab664fa2017-03-24 16:07:20 +00001237 SkScalar perpDot = v0.cross(v1);
Jim Van Verth91af7272017-01-27 14:15:54 -05001238 if (SkScalarNearlyZero(perpDot)) {
1239 // nearly parallel, just treat as straight line and continue
1240 fInitPoints[1] = p;
1241 return;
1242 }
1243
1244 // if perpDot > 0, winding is ccw
1245 fDirection = (perpDot > 0) ? -1 : 1;
1246
1247 // add first quad
Jim Van Verth76387852017-05-16 16:55:16 -04001248 if (!compute_normal(fInitPoints[0], fInitPoints[1], fDirection, &fFirstOutset)) {
Jim Van Verth91af7272017-01-27 14:15:54 -05001249 // first two points are incident, make the third point the second and continue
1250 fInitPoints[1] = p;
1251 return;
1252 }
1253
Jim Van Verth76387852017-05-16 16:55:16 -04001254 fFirstOutset *= fRadius;
Jim Van Verth91af7272017-01-27 14:15:54 -05001255 fFirstPoint = fInitPoints[0];
Jim Van Verth76387852017-05-16 16:55:16 -04001256 fFirstVertexIndex = fPositions.count();
1257 fPrevOutset = fFirstOutset;
Jim Van Verth91af7272017-01-27 14:15:54 -05001258 fPrevPoint = fFirstPoint;
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -04001259 fPrevUmbraIndex = -1;
Jim Van Verth91af7272017-01-27 14:15:54 -05001260
Brian Salomon66085ed2017-02-02 15:39:34 -05001261 this->addInnerPoint(fFirstPoint);
Jim Van Verth76387852017-05-16 16:55:16 -04001262 fPrevUmbraIndex = fFirstVertexIndex;
Brian Salomon66085ed2017-02-02 15:39:34 -05001263
1264 if (!fTransparent) {
1265 SkPoint clipPoint;
Jim Van Verth76387852017-05-16 16:55:16 -04001266 bool isOutside = this->clipUmbraPoint(fPositions[fFirstVertexIndex], fCentroid, &clipPoint);
Brian Salomon66085ed2017-02-02 15:39:34 -05001267 if (isOutside) {
1268 *fPositions.push() = clipPoint;
1269 *fColors.push() = fUmbraColor;
1270 }
1271 fPrevUmbraOutside = isOutside;
1272 fFirstUmbraOutside = isOutside;
1273 }
1274
Jim Van Verth76387852017-05-16 16:55:16 -04001275 SkPoint newPoint = fFirstPoint + fFirstOutset;
Jim Van Verth91af7272017-01-27 14:15:54 -05001276 *fPositions.push() = newPoint;
1277 *fColors.push() = fPenumbraColor;
Jim Van Verth76387852017-05-16 16:55:16 -04001278 this->addEdge(fInitPoints[1], fFirstOutset);
Jim Van Verth91af7272017-01-27 14:15:54 -05001279
1280 // to ensure we skip this block next time
1281 *fInitPoints.push() = p;
1282 }
1283
1284 SkVector normal;
Jim Van Verthda965502017-04-11 15:29:14 -04001285 if (compute_normal(fPrevPoint, p, fDirection, &normal)) {
1286 normal *= fRadius;
1287 this->addArc(normal, true);
1288 this->addEdge(p, normal);
Jim Van Verth91af7272017-01-27 14:15:54 -05001289 }
1290}
1291
Brian Salomonab664fa2017-03-24 16:07:20 +00001292bool SkSpotShadowTessellator::addInnerPoint(const SkPoint& pathPoint) {
1293 SkPoint umbraPoint;
1294 if (!fValidUmbra) {
1295 SkVector v = fCentroid - pathPoint;
1296 v *= 0.95f;
1297 umbraPoint = pathPoint + v;
Jim Van Verth91af7272017-01-27 14:15:54 -05001298 } else {
Brian Salomonab664fa2017-03-24 16:07:20 +00001299 umbraPoint = fUmbraPolygon[this->getClosestUmbraPoint(pathPoint)];
Jim Van Verth91af7272017-01-27 14:15:54 -05001300 }
Brian Salomon66085ed2017-02-02 15:39:34 -05001301
Jim Van Verth91af7272017-01-27 14:15:54 -05001302 fPrevPoint = pathPoint;
Brian Salomonab664fa2017-03-24 16:07:20 +00001303
1304 // merge "close" points
Jim Van Verthe7e1d9d2017-05-01 16:06:48 -04001305 if (fPrevUmbraIndex == -1 ||
Brian Salomonab664fa2017-03-24 16:07:20 +00001306 !duplicate_pt(umbraPoint, fPositions[fPrevUmbraIndex])) {
1307 *fPositions.push() = umbraPoint;
1308 *fColors.push() = fUmbraColor;
1309
1310 return false;
1311 } else {
1312 return true;
1313 }
Jim Van Verth91af7272017-01-27 14:15:54 -05001314}
1315
Jim Van Verthefe3ded2017-01-30 13:11:45 -05001316void SkSpotShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVector& nextNormal) {
Brian Salomon66085ed2017-02-02 15:39:34 -05001317 // add next umbra point
Brian Salomonab664fa2017-03-24 16:07:20 +00001318 bool duplicate = this->addInnerPoint(nextPoint);
1319 int prevPenumbraIndex = duplicate ? fPositions.count()-1 : fPositions.count()-2;
1320 int currUmbraIndex = duplicate ? fPrevUmbraIndex : fPositions.count()-1;
Brian Salomon66085ed2017-02-02 15:39:34 -05001321
Brian Salomonab664fa2017-03-24 16:07:20 +00001322 if (!duplicate) {
1323 // add to center fan if transparent or centroid showing
1324 if (fTransparent) {
1325 *fIndices.push() = 0;
1326 *fIndices.push() = fPrevUmbraIndex;
1327 *fIndices.push() = currUmbraIndex;
1328 // otherwise add to clip ring
1329 } else {
Brian Salomon66085ed2017-02-02 15:39:34 -05001330 SkPoint clipPoint;
Brian Salomonab664fa2017-03-24 16:07:20 +00001331 bool isOutside = this->clipUmbraPoint(fPositions[currUmbraIndex], fCentroid,
1332 &clipPoint);
Brian Salomon66085ed2017-02-02 15:39:34 -05001333 if (isOutside) {
1334 *fPositions.push() = clipPoint;
1335 *fColors.push() = fUmbraColor;
1336
1337 *fIndices.push() = fPrevUmbraIndex;
1338 *fIndices.push() = currUmbraIndex;
1339 *fIndices.push() = currUmbraIndex + 1;
1340 if (fPrevUmbraOutside) {
1341 // fill out quad
1342 *fIndices.push() = fPrevUmbraIndex;
1343 *fIndices.push() = currUmbraIndex + 1;
1344 *fIndices.push() = fPrevUmbraIndex + 1;
1345 }
1346 } else if (fPrevUmbraOutside) {
1347 // add tri
1348 *fIndices.push() = fPrevUmbraIndex;
1349 *fIndices.push() = currUmbraIndex;
1350 *fIndices.push() = fPrevUmbraIndex + 1;
1351 }
1352 fPrevUmbraOutside = isOutside;
1353 }
1354 }
1355
1356 // add next penumbra point and quad
Jim Van Verth91af7272017-01-27 14:15:54 -05001357 SkPoint newPoint = nextPoint + nextNormal;
1358 *fPositions.push() = newPoint;
1359 *fColors.push() = fPenumbraColor;
1360
Brian Salomonab664fa2017-03-24 16:07:20 +00001361 if (!duplicate) {
1362 *fIndices.push() = fPrevUmbraIndex;
1363 *fIndices.push() = prevPenumbraIndex;
1364 *fIndices.push() = currUmbraIndex;
1365 }
Jim Van Verth91af7272017-01-27 14:15:54 -05001366
Brian Salomon66085ed2017-02-02 15:39:34 -05001367 *fIndices.push() = prevPenumbraIndex;
Jim Van Verth91af7272017-01-27 14:15:54 -05001368 *fIndices.push() = fPositions.count() - 1;
Brian Salomon66085ed2017-02-02 15:39:34 -05001369 *fIndices.push() = currUmbraIndex;
Jim Van Verth91af7272017-01-27 14:15:54 -05001370
Brian Salomon66085ed2017-02-02 15:39:34 -05001371 fPrevUmbraIndex = currUmbraIndex;
Jim Van Verth76387852017-05-16 16:55:16 -04001372 fPrevOutset = nextNormal;
Jim Van Verth91af7272017-01-27 14:15:54 -05001373}
Brian Salomon958fbc42017-01-30 17:01:28 -05001374
1375///////////////////////////////////////////////////////////////////////////////////////////////////
1376
Brian Salomonaff27a22017-02-06 15:47:44 -05001377sk_sp<SkVertices> SkShadowTessellator::MakeAmbient(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -04001378 const SkPoint3& zPlane, bool transparent) {
1379 SkAmbientShadowTessellator ambientTess(path, ctm, zPlane, transparent);
Brian Salomonaff27a22017-02-06 15:47:44 -05001380 return ambientTess.releaseVertices();
Brian Salomon958fbc42017-01-30 17:01:28 -05001381}
1382
Brian Salomonaff27a22017-02-06 15:47:44 -05001383sk_sp<SkVertices> SkShadowTessellator::MakeSpot(const SkPath& path, const SkMatrix& ctm,
Jim Van Verthe308a122017-05-08 14:19:30 -04001384 const SkPoint3& zPlane, const SkPoint3& lightPos,
Jim Van Verth060d9822017-05-04 09:58:17 -04001385 SkScalar lightRadius, bool transparent) {
Jim Van Verthe308a122017-05-08 14:19:30 -04001386 SkSpotShadowTessellator spotTess(path, ctm, zPlane, lightPos, lightRadius, transparent);
Brian Salomonaff27a22017-02-06 15:47:44 -05001387 return spotTess.releaseVertices();
Brian Salomon958fbc42017-01-30 17:01:28 -05001388}