diff options
-rw-r--r-- | include/utils/SkCamera.h | 8 | ||||
-rw-r--r-- | samplecode/SampleAndroidShadows.cpp | 74 | ||||
-rw-r--r-- | src/utils/SkCamera.cpp | 24 | ||||
-rwxr-xr-x | src/utils/SkInsetConvexPolygon.cpp | 77 | ||||
-rwxr-xr-x | src/utils/SkInsetConvexPolygon.h | 35 | ||||
-rwxr-xr-x[-rw-r--r--] | src/utils/SkShadowTessellator.cpp | 471 | ||||
-rw-r--r-- | tests/InsetConvexPolyTest.cpp | 14 |
7 files changed, 542 insertions, 161 deletions
diff --git a/include/utils/SkCamera.h b/include/utils/SkCamera.h index 4b77ec685d..3cb13fc19b 100644 --- a/include/utils/SkCamera.h +++ b/include/utils/SkCamera.h @@ -103,10 +103,10 @@ public: void update(); void patchToMatrix(const SkPatch3D&, SkMatrix* matrix) const; - SkPoint3D fLocation; - SkPoint3D fAxis; - SkPoint3D fZenith; - SkPoint3D fObserver; + SkPoint3D fLocation; // origin of the camera's space + SkPoint3D fAxis; // view direction + SkPoint3D fZenith; // up direction + SkPoint3D fObserver; // eye position (may not be the same as the origin) private: mutable SkMatrix fOrientation; diff --git a/samplecode/SampleAndroidShadows.cpp b/samplecode/SampleAndroidShadows.cpp index bb56b7ed17..0c0baaba10 100644 --- a/samplecode/SampleAndroidShadows.cpp +++ b/samplecode/SampleAndroidShadows.cpp @@ -37,6 +37,7 @@ class ShadowsView : public SampleView { SkPoint3 fLightPos; SkScalar fZDelta; SkScalar fAnimTranslate; + SkScalar fAnimAngle; bool fShowAmbient; bool fShowSpot; @@ -48,6 +49,7 @@ public: ShadowsView() : fZDelta(0) , fAnimTranslate(0) + , fAnimAngle(0) , fShowAmbient(true) , fShowSpot(true) , fUseAlt(true) @@ -403,10 +405,12 @@ protected: canvas->drawRRect(shadowRRect, paint); } - void drawShadowedPath(SkCanvas* canvas, const SkPath& path, SkScalar zValue, + void drawShadowedPath(SkCanvas* canvas, const SkPath& path, + std::function<SkScalar(SkScalar, SkScalar)> zFunc, const SkPaint& paint, SkScalar ambientAlpha, const SkPoint3& lightPos, SkScalar lightWidth, SkScalar spotAlpha) { #ifdef USE_SHADOW_UTILS + SkScalar zValue = zFunc(0, 0); if (fUseAlt) { if (fShowAmbient) { this->drawAmbientShadowAlt(canvas, path, zValue, ambientAlpha); @@ -422,14 +426,13 @@ protected: spotAlpha = 0; } - SkShadowUtils::DrawShadow(canvas, path, - zValue, - lightPos, lightWidth, - ambientAlpha, spotAlpha, SK_ColorBLACK); - //SkShadowUtils::DrawUncachedShadow(canvas, path, - // [zValue](SkScalar, SkScalar) { return zValue; }, - // lightPos, lightWidth, - // ambientAlpha, spotAlpha, SK_ColorBLACK); + //SkShadowUtils::DrawShadow(canvas, path, + // zValue, + // lightPos, lightWidth, + // ambientAlpha, spotAlpha, SK_ColorBLACK); + SkShadowUtils::DrawUncachedShadow(canvas, path, zFunc, + lightPos, lightWidth, + ambientAlpha, spotAlpha, SK_ColorBLACK); } #else if (fShowAmbient) { @@ -475,39 +478,52 @@ protected: canvas->translate(200, 90); lightPos.fX += 200; lightPos.fY += 90; - this->drawShadowedPath(canvas, fRRPath, SkTMax(1.0f, 2+fZDelta), paint, kAmbientAlpha, + SkScalar zValue = SkTMax(1.0f, 2 + fZDelta); + std::function<SkScalar(SkScalar, SkScalar)> zFunc = + [zValue](SkScalar, SkScalar) { return zValue; }; + this->drawShadowedPath(canvas, fRRPath, zFunc, paint, kAmbientAlpha, lightPos, kLightWidth, kSpotAlpha); paint.setColor(SK_ColorRED); canvas->translate(250, 0); lightPos.fX += 250; - this->drawShadowedPath(canvas, fRectPath, SkTMax(1.0f, 4+fZDelta), paint, kAmbientAlpha, + zValue = SkTMax(1.0f, 4 + fZDelta); + zFunc = [zValue](SkScalar, SkScalar) { return zValue; }; + this->drawShadowedPath(canvas, fRectPath, zFunc, paint, kAmbientAlpha, lightPos, kLightWidth, kSpotAlpha); paint.setColor(SK_ColorBLUE); canvas->translate(-250, 110); lightPos.fX -= 250; lightPos.fY += 110; - this->drawShadowedPath(canvas, fCirclePath, SkTMax(1.0f, 8+fZDelta), paint, kAmbientAlpha, + zValue = SkTMax(1.0f, 8 + fZDelta); + zFunc = [zValue](SkScalar, SkScalar) { return zValue; }; + this->drawShadowedPath(canvas, fCirclePath, zFunc, paint, kAmbientAlpha, lightPos, kLightWidth, 0.5f); paint.setColor(SK_ColorGREEN); canvas->translate(250, 0); lightPos.fX += 250; - this->drawShadowedPath(canvas, fRRPath, SkTMax(1.0f, 64+fZDelta), paint, kAmbientAlpha, + zValue = SkTMax(1.0f, 64 + fZDelta); + zFunc = [zValue](SkScalar, SkScalar) { return zValue; }; + this->drawShadowedPath(canvas, fRRPath, zFunc, paint, kAmbientAlpha, lightPos, kLightWidth, kSpotAlpha); paint.setColor(SK_ColorYELLOW); canvas->translate(-250, 110); lightPos.fX -= 250; lightPos.fY += 110; - this->drawShadowedPath(canvas, fFunkyRRPath, SkTMax(1.0f, 8+fZDelta), paint, kAmbientAlpha, + zValue = SkTMax(1.0f, 8 + fZDelta); + zFunc = [zValue](SkScalar, SkScalar) { return zValue; }; + this->drawShadowedPath(canvas, fFunkyRRPath, zFunc, paint, kAmbientAlpha, lightPos, kLightWidth, kSpotAlpha); paint.setColor(SK_ColorCYAN); canvas->translate(250, 0); lightPos.fX += 250; - this->drawShadowedPath(canvas, fCubicPath, SkTMax(1.0f, 16 + fZDelta), paint, + zValue = SkTMax(1.0f, 16 + fZDelta); + zFunc = [zValue](SkScalar, SkScalar) { return zValue; }; + this->drawShadowedPath(canvas, fCubicPath, zFunc, paint, kAmbientAlpha, lightPos, kLightWidth, kSpotAlpha); // circular reveal @@ -520,17 +536,19 @@ protected: canvas->translate(-125, 60); lightPos.fX -= 125; lightPos.fY += 60; - this->drawShadowedPath(canvas, tmpPath, SkTMax(1.0f, 32 + fZDelta), paint, .1f, + zValue = SkTMax(1.0f, 32 + fZDelta); + zFunc = [zValue](SkScalar, SkScalar) { return zValue; }; + this->drawShadowedPath(canvas, tmpPath, zFunc, paint, .1f, lightPos, kLightWidth, .5f); // perspective paths SkPoint pivot = SkPoint::Make(fWideRectPath.getBounds().width()/2, fWideRectPath.getBounds().height()/2); - SkPoint translate = SkPoint::Make(50, 450); + SkPoint translate = SkPoint::Make(100, 450); paint.setColor(SK_ColorWHITE); Sk3DView view; view.save(); - view.rotateX(10); + view.rotateX(fAnimAngle); SkMatrix persp; view.getMatrix(&persp); persp.preTranslate(-pivot.fX, -pivot.fY); @@ -539,14 +557,20 @@ protected: lightPos = fLightPos; lightPos.fX += pivot.fX + translate.fX; lightPos.fY += pivot.fY + translate.fY; - this->drawShadowedPath(canvas, fWideRectPath, SkTMax(1.0f, 16 + fZDelta), paint, .1f, + zValue = SkTMax(1.0f, 16 + fZDelta); + SkScalar radians = SkDegreesToRadians(fAnimAngle); + zFunc = [zValue, pivot, radians](SkScalar x, SkScalar y) { + return SkScalarSin(-radians)*y + + zValue - SkScalarSin(-radians)*pivot.fY; + }; + this->drawShadowedPath(canvas, fWideRectPath, zFunc, paint, .1f, lightPos, kLightWidth, .5f); pivot = SkPoint::Make(fWideOvalPath.getBounds().width() / 2, fWideOvalPath.getBounds().height() / 2); - translate = SkPoint::Make(50, 600); + translate = SkPoint::Make(100, 600); view.restore(); - view.rotateY(10); + view.rotateY(fAnimAngle); view.getMatrix(&persp); persp.preTranslate(-pivot.fX, -pivot.fY); persp.postTranslate(pivot.fX + translate.fX, pivot.fY + translate.fY); @@ -554,12 +578,18 @@ protected: lightPos = fLightPos; lightPos.fX += pivot.fX + translate.fX; lightPos.fY += pivot.fY + translate.fY; - this->drawShadowedPath(canvas, fWideOvalPath, SkTMax(1.0f, 32 + fZDelta), paint, .1f, + zValue = SkTMax(1.0f, 32 + fZDelta); + zFunc = [zValue, pivot, radians](SkScalar x, SkScalar y) { + return -SkScalarSin(radians)*x + + zValue + SkScalarSin(radians)*pivot.fX; + }; + this->drawShadowedPath(canvas, fWideOvalPath, zFunc, paint, .1f, lightPos, kLightWidth, .5f); } bool onAnimate(const SkAnimTimer& timer) override { fAnimTranslate = timer.pingPong(30, 0, 200, -200); + fAnimAngle = timer.pingPong(15, 0, 0, 20); return true; } diff --git a/src/utils/SkCamera.cpp b/src/utils/SkCamera.cpp index 23ab396c98..cb364a504e 100644 --- a/src/utils/SkCamera.cpp +++ b/src/utils/SkCamera.cpp @@ -214,6 +214,7 @@ void SkCamera3D::update() { void SkCamera3D::doUpdate() const { SkUnit3D axis, zenith, cross; + // construct a orthonormal basis of cross (x), zenith (y), and axis (z) fAxis.normalize(&axis); { @@ -234,6 +235,20 @@ void SkCamera3D::doUpdate() const { SkScalar y = fObserver.fY; SkScalar z = fObserver.fZ; + // Looking along the view axis we have: + // + // /|\ zenith + // | + // | + // | * observer (projected on XY plane) + // | + // |____________\ cross + // / + // + // So this does a z-shear along the view axis based on the observer's x and y values, + // and scales in x and y relative to the negative of the observer's z value + // (the observer is in the negative z direction). + orien->set(SkMatrix::kMScaleX, x * axis.fX - z * cross.fX); orien->set(SkMatrix::kMSkewX, x * axis.fY - z * cross.fY); orien->set(SkMatrix::kMTransX, x * axis.fZ - z * cross.fZ); @@ -264,6 +279,15 @@ void SkCamera3D::patchToMatrix(const SkPatch3D& quilt, SkMatrix* matrix) const { dot = SkUnit3D::Dot(*SkTCast<const SkUnit3D*>(&diff), *SkTCast<const SkUnit3D*>(SkTCast<const SkScalar*>(&fOrientation) + 6)); + // This multiplies fOrientation by the matrix [quilt.fU quilt.fV diff] -- U, V, and diff are + // column vectors in the matrix -- then divides by the length of the projection of diff onto + // the view axis (which is 'dot'). This transforms the patch (which transforms from local path + // space to world space) into view space (since fOrientation transforms from world space to + // view space). + // + // The divide by 'dot' isn't strictly necessary as the homogeneous divide would do much the + // same thing (it's just scaling the entire matrix by 1/dot). It looks like it's normalizing + // the matrix into some canonical space. patchPtr = (const SkScalar*)&quilt; matrix->set(SkMatrix::kMScaleX, SkScalarDotDiv(3, patchPtr, 1, mapPtr, 1, dot)); matrix->set(SkMatrix::kMSkewY, SkScalarDotDiv(3, patchPtr, 1, mapPtr+3, 1, dot)); diff --git a/src/utils/SkInsetConvexPolygon.cpp b/src/utils/SkInsetConvexPolygon.cpp index 93bbae9b9a..bb4694264d 100755 --- a/src/utils/SkInsetConvexPolygon.cpp +++ b/src/utils/SkInsetConvexPolygon.cpp @@ -49,17 +49,46 @@ static int get_winding(const SkPoint* polygonVerts, int polygonSize) { return 0; } -// Perpendicularly offset line segment p0-p1 'distance' units in the direction specified by 'dir' -static void inset_edge(const SkPoint& p0, const SkPoint& p1, SkScalar distance, int dir, - InsetSegment* inset) { - SkASSERT(dir == -1 || dir == 1); - // compute perpendicular - SkVector perp; - perp.fX = p0.fY - p1.fY; - perp.fY = p1.fX - p0.fX; - perp.setLength(distance*dir); - inset->fP0 = p0 + perp; - inset->fP1 = p1 + perp; +// Offset line segment p0-p1 'd0' and 'd1' units in the direction specified by 'side' +bool SkOffsetSegment(const SkPoint& p0, const SkPoint& p1, SkScalar d0, SkScalar d1, + int side, SkPoint* offset0, SkPoint* offset1) { + SkASSERT(side == -1 || side == 1); + SkVector perp = SkVector::Make(p0.fY - p1.fY, p1.fX - p0.fX); + if (SkScalarNearlyEqual(d0, d1)) { + // if distances are equal, can just outset by the perpendicular + perp.setLength(d0*side); + *offset0 = p0 + perp; + *offset1 = p1 + perp; + } else { + // Otherwise we need to compute the outer tangent. + // See: http://www.ambrsoft.com/TrigoCalc/Circles2/Circles2Tangent_.htm + if (d0 < d1) { + side = -side; + } + SkScalar dD = d0 - d1; + // if one circle is inside another, we can't compute an offset + if (dD*dD >= p0.distanceToSqd(p1)) { + return false; + } + SkPoint outerTangentIntersect = SkPoint::Make((p1.fX*d0 - p0.fX*d1) / dD, + (p1.fY*d0 - p0.fY*d1) / dD); + + SkScalar d0sq = d0*d0; + SkVector dP = outerTangentIntersect - p0; + SkScalar dPlenSq = dP.lengthSqd(); + SkScalar discrim = SkScalarSqrt(dPlenSq - d0sq); + offset0->fX = p0.fX + (d0sq*dP.fX - side*d0*dP.fY*discrim) / dPlenSq; + offset0->fY = p0.fY + (d0sq*dP.fY + side*d0*dP.fX*discrim) / dPlenSq; + + SkScalar d1sq = d1*d1; + dP = outerTangentIntersect - p1; + dPlenSq = dP.lengthSqd(); + discrim = SkScalarSqrt(dPlenSq - d1sq); + offset1->fX = p1.fX + (d1sq*dP.fX - side*d1*dP.fY*discrim) / dPlenSq; + offset1->fY = p1.fY + (d1sq*dP.fY + side*d1*dP.fX*discrim) / dPlenSq; + } + + return true; } // Compute the intersection 'p' between segments s0 and s1, if any. @@ -147,7 +176,8 @@ static bool is_convex(const SkTDArray<SkPoint>& poly) { // Note: the assumption is that inputPolygon is convex and has no coincident points. // bool SkInsetConvexPolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize, - SkScalar insetDistance, SkTDArray<SkPoint>* insetPolygon) { + std::function<SkScalar(int index)> insetDistanceFunc, + SkTDArray<SkPoint>* insetPolygon) { if (inputPolygonSize < 3) { return false; } @@ -168,8 +198,10 @@ bool SkInsetConvexPolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize SkAutoSTMalloc<64, EdgeData> edgeData(inputPolygonSize); for (int i = 0; i < inputPolygonSize; ++i) { int j = (i + 1) % inputPolygonSize; - inset_edge(inputPolygonVerts[i], inputPolygonVerts[j], insetDistance, winding, - &edgeData[i].fInset); + SkOffsetSegment(inputPolygonVerts[i], inputPolygonVerts[j], + insetDistanceFunc(i), insetDistanceFunc(j), + winding, + &edgeData[i].fInset.fP0, &edgeData[i].fInset.fP1); edgeData[i].fIntersection = edgeData[i].fInset.fP0; edgeData[i].fTValue = SK_ScalarMin; edgeData[i].fValid = true; @@ -231,14 +263,27 @@ bool SkInsetConvexPolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize } } - // store all the valid intersections + // store all the valid intersections that aren't nearly coincident + // TODO: look at the main algorithm and see if we can detect these better + static constexpr SkScalar kCleanupTolerance = 0.01f; + insetPolygon->reset(); insetPolygon->setReserve(insetVertexCount); + currIndex = -1; for (int i = 0; i < inputPolygonSize; ++i) { - if (edgeData[i].fValid) { + if (edgeData[i].fValid && (currIndex == -1 || + !edgeData[i].fIntersection.equalsWithinTolerance((*insetPolygon)[currIndex], + kCleanupTolerance))) { *insetPolygon->push() = edgeData[i].fIntersection; + currIndex++; } } + // make sure the first and last points aren't coincident + if (currIndex >= 1 && + (*insetPolygon)[0].equalsWithinTolerance((*insetPolygon)[currIndex], + kCleanupTolerance)) { + insetPolygon->pop(); + } SkASSERT(is_convex(*insetPolygon)); return (insetPolygon->count() >= 3); diff --git a/src/utils/SkInsetConvexPolygon.h b/src/utils/SkInsetConvexPolygon.h index 3ab7558a25..1d5a19c176 100755 --- a/src/utils/SkInsetConvexPolygon.h +++ b/src/utils/SkInsetConvexPolygon.h @@ -8,20 +8,49 @@ #ifndef SkInsetConvexPolygon_DEFINED #define SkInsetConvexPolygon_DEFINED +#include <functional> + #include "SkTDArray.h" #include "SkPoint.h" - /** +/** * Generates a polygon that is inset a given distance from the boundary of a given convex polygon. * * @param inputPolygonVerts Array of points representing the vertices of the original polygon. * It should be convex and have no coincident points. * @param inputPolygonSize Number of vertices in the original polygon. - * @param insetDistance How far we wish to inset the polygon. This should be a positive value. + * @param insetDistanceFunc How far we wish to inset the polygon for a given index in the array. + * This should return a positive value. * @param insetPolygon The resulting inset polygon, if any. * @return true if an inset polygon exists, false otherwise. */ bool SkInsetConvexPolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize, - SkScalar insetDistance, SkTDArray<SkPoint>* insetPolygon); + std::function<SkScalar(int index)> insetDistanceFunc, + SkTDArray<SkPoint>* insetPolygon); + +inline bool SkInsetConvexPolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize, + SkScalar inset, + SkTDArray<SkPoint>* insetPolygon) { + return SkInsetConvexPolygon(inputPolygonVerts, inputPolygonSize, + [inset](int) { return inset; }, + insetPolygon); +} + +/** + * Offset a segment by the given distance at each point. + * Uses the outer tangents of two circles centered on each endpoint. + * See: https://en.wikipedia.org/wiki/Tangent_lines_to_circles + * + * @param p0 First endpoint. + * @param p1 Second endpoint. + * @param d0 Offset distance from first endpoint. + * @param d1 Offset distance from second endpoint. + * @param side Indicates whether we want to offset to the left (1) or right (-1) side of segment. + * @param offset0 First endpoint of offset segment. + * @param offset1 Second endpoint of offset segment. + * @return true if an offset segment exists, false otherwise. + */ +bool SkOffsetSegment(const SkPoint& p0, const SkPoint& p1, SkScalar d0, SkScalar d1, + int side, SkPoint* offset0, SkPoint* offset1); #endif diff --git a/src/utils/SkShadowTessellator.cpp b/src/utils/SkShadowTessellator.cpp index fa3f3f479f..7d29b063a0 100644..100755 --- a/src/utils/SkShadowTessellator.cpp +++ b/src/utils/SkShadowTessellator.cpp @@ -35,9 +35,13 @@ public: } protected: + static constexpr auto kMinHeight = 0.1f; + int vertexCount() const { return fPositions.count(); } int indexCount() const { return fIndices.count(); } + bool setZOffset(const SkRect& bounds, bool perspective); + virtual void handleLine(const SkPoint& p) = 0; void handleLine(const SkMatrix& m, SkPoint* p); @@ -48,13 +52,18 @@ protected: void handleConic(const SkMatrix& m, SkPoint pts[3], SkScalar w); - void addArc(const SkVector& nextNormal); - void finishArcAndAddEdge(const SkVector& nextPoint, const SkVector& nextNormal); - virtual void addEdge(const SkVector& nextPoint, const SkVector& nextNormal) = 0; + bool setTransformedHeightFunc(const SkMatrix& ctm); + + void addArc(const SkVector& nextNormal, bool finishArc); - SkShadowTessellator::HeightFunc fHeightFunc; + SkShadowTessellator::HeightFunc fHeightFunc; + std::function<SkScalar(const SkPoint&)> fTransformedHeightFunc; + SkScalar fZOffset; + // members for perspective height function + SkScalar fZParams[3]; + SkScalar fPartialDeterminants[3]; - // first three points + // first two points SkTDArray<SkPoint> fInitPoints; // temporary buffer SkTDArray<SkPoint> fPointBuffer; @@ -80,16 +89,16 @@ protected: SkPoint fPrevPoint; }; -static bool compute_normal(const SkPoint& p0, const SkPoint& p1, SkScalar radius, SkScalar dir, +static bool compute_normal(const SkPoint& p0, const SkPoint& p1, SkScalar dir, SkVector* newNormal) { SkVector normal; // compute perpendicular normal.fX = p0.fY - p1.fY; normal.fY = p1.fX - p0.fX; + normal *= dir; if (!normal.normalize()) { return false; } - normal *= radius*dir; *newNormal = normal; return true; } @@ -112,6 +121,7 @@ static void compute_radial_steps(const SkVector& v1, const SkVector& v2, SkScala SkBaseShadowTessellator::SkBaseShadowTessellator(SkShadowTessellator::HeightFunc heightFunc, bool transparent) : fHeightFunc(heightFunc) + , fZOffset(0) , fFirstVertex(-1) , fSucceeded(false) , fTransparent(transparent) @@ -122,6 +132,31 @@ SkBaseShadowTessellator::SkBaseShadowTessellator(SkShadowTessellator::HeightFunc // child classes will set reserve for positions, colors and indices } +bool SkBaseShadowTessellator::setZOffset(const SkRect& bounds, bool perspective) { + SkScalar minZ = fHeightFunc(bounds.fLeft, bounds.fTop); + if (perspective) { + SkScalar z = fHeightFunc(bounds.fLeft, bounds.fBottom); + if (z < minZ) { + minZ = z; + } + z = fHeightFunc(bounds.fRight, bounds.fTop); + if (z < minZ) { + minZ = z; + } + z = fHeightFunc(bounds.fRight, bounds.fBottom); + if (z < minZ) { + minZ = z; + } + } + + if (minZ < kMinHeight) { + fZOffset = -minZ + kMinHeight; + return true; + } + + return false; +} + // tesselation tolerance values, in device space pixels #if SK_SUPPORT_GPU static const SkScalar kQuadTolerance = 0.2f; @@ -180,6 +215,9 @@ void SkBaseShadowTessellator::handleCubic(const SkMatrix& m, SkPoint pts[4]) { } void SkBaseShadowTessellator::handleConic(const SkMatrix& m, SkPoint pts[3], SkScalar w) { + if (m.hasPerspective()) { + w = SkConic::TransformW(pts, w, m); + } m.mapPoints(pts, 3); SkAutoConicToQuads quadder; const SkPoint* quads = quadder.computeQuads(pts, w, kConicTolerance); @@ -196,36 +234,89 @@ void SkBaseShadowTessellator::handleConic(const SkMatrix& m, SkPoint pts[3], SkS } } -void SkBaseShadowTessellator::addArc(const SkVector& nextNormal) { +void SkBaseShadowTessellator::addArc(const SkVector& nextNormal, bool finishArc) { // fill in fan from previous quad SkScalar rotSin, rotCos; int numSteps; compute_radial_steps(fPrevNormal, nextNormal, fRadius, &rotSin, &rotCos, &numSteps); SkVector prevNormal = fPrevNormal; for (int i = 0; i < numSteps; ++i) { - SkVector nextNormal; - nextNormal.fX = prevNormal.fX*rotCos - prevNormal.fY*rotSin; - nextNormal.fY = prevNormal.fY*rotCos + prevNormal.fX*rotSin; - *fPositions.push() = fPrevPoint + nextNormal; + SkVector currNormal; + currNormal.fX = prevNormal.fX*rotCos - prevNormal.fY*rotSin; + currNormal.fY = prevNormal.fY*rotCos + prevNormal.fX*rotSin; + *fPositions.push() = fPrevPoint + currNormal; *fColors.push() = fPenumbraColor; *fIndices.push() = fPrevUmbraIndex; *fIndices.push() = fPositions.count() - 2; *fIndices.push() = fPositions.count() - 1; - prevNormal = nextNormal; + prevNormal = currNormal; } + if (finishArc) { + *fPositions.push() = fPrevPoint + nextNormal; + *fColors.push() = fPenumbraColor; + *fIndices.push() = fPrevUmbraIndex; + *fIndices.push() = fPositions.count() - 2; + *fIndices.push() = fPositions.count() - 1; + } + fPrevNormal = nextNormal; } -void SkBaseShadowTessellator::finishArcAndAddEdge(const SkPoint& nextPoint, - const SkVector& nextNormal) { - // close out previous arc - *fPositions.push() = fPrevPoint + nextNormal; - *fColors.push() = fPenumbraColor; - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 2; - *fIndices.push() = fPositions.count() - 1; +bool SkBaseShadowTessellator::setTransformedHeightFunc(const SkMatrix& ctm) { + if (!ctm.hasPerspective()) { + fTransformedHeightFunc = [this](const SkPoint& p) { + return this->fHeightFunc(0, 0); + }; + } else { + SkMatrix ctmInverse; + if (!ctm.invert(&ctmInverse)) { + return false; + } + SkScalar C = fHeightFunc(0, 0); + SkScalar A = fHeightFunc(1, 0) - C; + SkScalar B = fHeightFunc(0, 1) - C; + + // multiply by transpose + fZParams[0] = ctmInverse[SkMatrix::kMScaleX] * A + + ctmInverse[SkMatrix::kMSkewY] * B + + ctmInverse[SkMatrix::kMPersp0] * C; + fZParams[1] = ctmInverse[SkMatrix::kMSkewX] * A + + ctmInverse[SkMatrix::kMScaleY] * B + + ctmInverse[SkMatrix::kMPersp1] * C; + fZParams[2] = ctmInverse[SkMatrix::kMTransX] * A + + ctmInverse[SkMatrix::kMTransY] * B + + ctmInverse[SkMatrix::kMPersp2] * C; + + // We use Cramer's rule to solve for the W value for a given post-divide X and Y, + // so pre-compute those values that are independent of X and Y. + // W is det(ctmInverse)/(PD[0]*X + PD[1]*Y + PD[2]) + fPartialDeterminants[0] = ctm[SkMatrix::kMSkewY] * ctm[SkMatrix::kMPersp1] - + ctm[SkMatrix::kMScaleY] * ctm[SkMatrix::kMPersp0]; + fPartialDeterminants[1] = ctm[SkMatrix::kMPersp0] * ctm[SkMatrix::kMSkewX] - + ctm[SkMatrix::kMPersp1] * ctm[SkMatrix::kMScaleX]; + fPartialDeterminants[2] = ctm[SkMatrix::kMScaleX] * ctm[SkMatrix::kMScaleY] - + ctm[SkMatrix::kMSkewX] * ctm[SkMatrix::kMSkewY]; + SkScalar ctmDeterminant = ctm[SkMatrix::kMTransX] * fPartialDeterminants[0] + + ctm[SkMatrix::kMTransY] * fPartialDeterminants[1] + + ctm[SkMatrix::kMPersp2] * fPartialDeterminants[2]; + + // Pre-bake the numerator of Cramer's rule into the zParams to avoid another multiply. + // TODO: this may introduce numerical instability, but I haven't seen any issues yet. + fZParams[0] *= ctmDeterminant; + fZParams[1] *= ctmDeterminant; + fZParams[2] *= ctmDeterminant; + + fTransformedHeightFunc = [this](const SkPoint& p) { + SkScalar denom = p.fX * this->fPartialDeterminants[0] + + p.fY * this->fPartialDeterminants[1] + + this->fPartialDeterminants[2]; + SkScalar w = SkScalarFastInvert(denom); + return (this->fZParams[0] * p.fX + this->fZParams[1] * p.fY + this->fZParams[2])*w + + this->fZOffset; + }; + } - this->addEdge(nextPoint, nextNormal); + return true; } @@ -239,26 +330,35 @@ public: private: void handleLine(const SkPoint& p) override; - void addEdge(const SkVector& nextPoint, const SkVector& nextNormal) override; + void addEdge(const SkVector& nextPoint, const SkVector& nextNormal); + + static constexpr auto kHeightFactor = 1.0f / 128.0f; + static constexpr auto kGeomFactor = 64.0f; + static constexpr auto kMaxEdgeLenSqr = 20 * 20; + SkScalar offset(SkScalar z) { + return z * kHeightFactor * kGeomFactor; + } + SkColor umbraColor(SkScalar z) { + SkScalar umbraAlpha = SkScalarInvert((1.0f + SkTMax(z*kHeightFactor, 0.0f))); + return SkColorSetARGB(255, 0, fAmbientAlpha * 255.9999f, umbraAlpha * 255.9999f); + } + + SkScalar fAmbientAlpha; int fCentroidCount; typedef SkBaseShadowTessellator INHERITED; }; -static const float kHeightFactor = 1.0f / 128.0f; -static const float kGeomFactor = 64.0f; - SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path, const SkMatrix& ctm, SkShadowTessellator::HeightFunc heightFunc, SkScalar ambientAlpha, bool transparent) - : INHERITED(heightFunc, transparent) { - // Set radius and colors - // TODO: vary colors and radius based on heightFunc + : INHERITED(heightFunc, transparent) + , fAmbientAlpha(ambientAlpha) { + // Set base colors SkScalar occluderHeight = heightFunc(0, 0); - fRadius = occluderHeight * kHeightFactor * kGeomFactor; SkScalar umbraAlpha = SkScalarInvert((1.0f + SkTMax(occluderHeight*kHeightFactor, 0.0f))); // umbraColor is the interior value, penumbraColor the exterior value. // umbraAlpha is the factor that is linearly interpolated from outside to inside, and @@ -267,6 +367,11 @@ SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path, fUmbraColor = SkColorSetARGB(255, 0, ambientAlpha * 255.9999f, umbraAlpha * 255.9999f); fPenumbraColor = SkColorSetARGB(255, 0, ambientAlpha * 255.9999f, 0); + // make sure we're not below the canvas plane + this->setZOffset(path.getBounds(), ctm.hasPerspective()); + + this->setTransformedHeightFunc(ctm); + // Outer ring: 3*numPts // Middle ring: numPts fPositions.setReserve(4 * path.countPoints()); @@ -311,28 +416,62 @@ SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path, } SkVector normal; - if (compute_normal(fPrevPoint, fFirstPoint, fRadius, fDirection, - &normal)) { - this->addArc(normal); + if (compute_normal(fPrevPoint, fFirstPoint, fDirection, &normal)) { + SkScalar z = fTransformedHeightFunc(fPrevPoint); + fRadius = this->offset(z); + SkVector scaledNormal(normal); + scaledNormal *= fRadius; + this->addArc(scaledNormal, true); + + // set up for final edge + z = fTransformedHeightFunc(fFirstPoint); + normal *= this->offset(z); + + // make sure we don't end up with a sharp alpha edge along the quad diagonal + if (fColors[fPrevUmbraIndex] != fColors[fFirstVertex] && + fFirstPoint.distanceToSqd(fPositions[fPrevUmbraIndex]) > kMaxEdgeLenSqr) { + SkPoint centerPoint = fPositions[fPrevUmbraIndex] + fFirstPoint; + centerPoint *= 0.5f; + *fPositions.push() = centerPoint; + *fColors.push() = SkPMLerp(fColors[fFirstVertex], fColors[fPrevUmbraIndex], 128); + SkVector midNormal = fPrevNormal + normal; + midNormal *= 0.5f; + *fPositions.push() = centerPoint + midNormal; + *fColors.push() = fPenumbraColor; - // close out previous arc - *fPositions.push() = fPrevPoint + normal; - *fColors.push() = fPenumbraColor; - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 2; - *fIndices.push() = fPositions.count() - 1; + *fIndices.push() = fPrevUmbraIndex; + *fIndices.push() = fPositions.count() - 3; + *fIndices.push() = fPositions.count() - 2; - // add final edge + *fIndices.push() = fPositions.count() - 3; + *fIndices.push() = fPositions.count() - 1; + *fIndices.push() = fPositions.count() - 2; + + fPrevUmbraIndex = fPositions.count() - 2; + } + + // final edge *fPositions.push() = fFirstPoint + normal; *fColors.push() = fPenumbraColor; - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 2; - *fIndices.push() = fFirstVertex; + if (fColors[fPrevUmbraIndex] > fColors[fFirstVertex]) { + *fIndices.push() = fPrevUmbraIndex; + *fIndices.push() = fPositions.count() - 2; + *fIndices.push() = fFirstVertex; - *fIndices.push() = fPositions.count() - 2; - *fIndices.push() = fPositions.count() - 1; - *fIndices.push() = fFirstVertex; + *fIndices.push() = fPositions.count() - 2; + *fIndices.push() = fPositions.count() - 1; + *fIndices.push() = fFirstVertex; + } else { + *fIndices.push() = fPrevUmbraIndex; + *fIndices.push() = fPositions.count() - 2; + *fIndices.push() = fPositions.count() - 1; + + *fIndices.push() = fPrevUmbraIndex; + *fIndices.push() = fPositions.count() - 1; + *fIndices.push() = fFirstVertex; + } + fPrevNormal = normal; } // finalize centroid @@ -347,9 +486,9 @@ SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path, // final fan if (fPositions.count() >= 3) { fPrevUmbraIndex = fFirstVertex; - fPrevNormal = normal; fPrevPoint = fFirstPoint; - this->addArc(fFirstNormal); + fRadius = this->offset(fTransformedHeightFunc(fPrevPoint)); + this->addArc(fFirstNormal, false); *fIndices.push() = fFirstVertex; *fIndices.push() = fPositions.count() - 1; @@ -379,8 +518,8 @@ void SkAmbientShadowTessellator::handleLine(const SkPoint& p) { fDirection = (perpDot > 0) ? -1 : 1; // add first quad - if (!compute_normal(fInitPoints[0], fInitPoints[1], fRadius, fDirection, - &fFirstNormal)) { + SkVector normal; + if (!compute_normal(fInitPoints[0], fInitPoints[1], fDirection, &normal)) { // first two points are incident, make the third point the second and continue fInitPoints[1] = p; return; @@ -388,45 +527,106 @@ void SkAmbientShadowTessellator::handleLine(const SkPoint& p) { fFirstPoint = fInitPoints[0]; fFirstVertex = fPositions.count(); + SkScalar z = fTransformedHeightFunc(fFirstPoint); + fFirstNormal = normal; + fFirstNormal *= this->offset(z); + fPrevNormal = fFirstNormal; fPrevPoint = fFirstPoint; fPrevUmbraIndex = fFirstVertex; - *fPositions.push() = fInitPoints[0]; - *fColors.push() = fUmbraColor; - *fPositions.push() = fInitPoints[0] + fFirstNormal; + *fPositions.push() = fFirstPoint; + *fColors.push() = this->umbraColor(z); + *fPositions.push() = fFirstPoint + fFirstNormal; *fColors.push() = fPenumbraColor; if (fTransparent) { - fPositions[0] += fInitPoints[0]; + fPositions[0] += fFirstPoint; fCentroidCount = 1; } - this->addEdge(fInitPoints[1], fFirstNormal); + + // add the first quad + z = fTransformedHeightFunc(fInitPoints[1]); + fRadius = this->offset(z); + fUmbraColor = this->umbraColor(z); + normal *= fRadius; + this->addEdge(fInitPoints[1], normal); // to ensure we skip this block next time *fInitPoints.push() = p; } SkVector normal; - if (compute_normal(fPositions[fPrevUmbraIndex], p, fRadius, fDirection, &normal)) { - this->addArc(normal); - this->finishArcAndAddEdge(p, normal); + if (compute_normal(fPositions[fPrevUmbraIndex], p, fDirection, &normal)) { + SkVector scaledNormal = normal; + scaledNormal *= fRadius; + this->addArc(scaledNormal, true); + SkScalar z = fTransformedHeightFunc(p); + fRadius = this->offset(z); + fUmbraColor = this->umbraColor(z); + normal *= fRadius; + this->addEdge(p, normal); } } void SkAmbientShadowTessellator::addEdge(const SkPoint& nextPoint, const SkVector& nextNormal) { + // make sure we don't end up with a sharp alpha edge along the quad diagonal + if (fColors[fPrevUmbraIndex] != fUmbraColor && + nextPoint.distanceToSqd(fPositions[fPrevUmbraIndex]) > kMaxEdgeLenSqr) { + SkPoint centerPoint = fPositions[fPrevUmbraIndex] + nextPoint; + centerPoint *= 0.5f; + *fPositions.push() = centerPoint; + *fColors.push() = SkPMLerp(fUmbraColor, fColors[fPrevUmbraIndex], 128); + SkVector midNormal = fPrevNormal + nextNormal; + midNormal *= 0.5f; + *fPositions.push() = centerPoint + midNormal; + *fColors.push() = fPenumbraColor; + + // set triangularization to get best interpolation of color + if (fColors[fPrevUmbraIndex] > fColors[fPositions.count() - 2]) { + *fIndices.push() = fPrevUmbraIndex; + *fIndices.push() = fPositions.count() - 3; + *fIndices.push() = fPositions.count() - 2; + + *fIndices.push() = fPositions.count() - 3; + *fIndices.push() = fPositions.count() - 1; + *fIndices.push() = fPositions.count() - 2; + } else { + *fIndices.push() = fPrevUmbraIndex; + *fIndices.push() = fPositions.count() - 2; + *fIndices.push() = fPositions.count() - 1; + + *fIndices.push() = fPrevUmbraIndex; + *fIndices.push() = fPositions.count() - 1; + *fIndices.push() = fPositions.count() - 3; + } + + fPrevUmbraIndex = fPositions.count() - 2; + } + // add next quad *fPositions.push() = nextPoint; *fColors.push() = fUmbraColor; *fPositions.push() = nextPoint + nextNormal; *fColors.push() = fPenumbraColor; - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 3; - *fIndices.push() = fPositions.count() - 2; + // set triangularization to get best interpolation of color + if (fColors[fPrevUmbraIndex] > fColors[fPositions.count() - 2]) { + *fIndices.push() = fPrevUmbraIndex; + *fIndices.push() = fPositions.count() - 3; + *fIndices.push() = fPositions.count() - 2; - *fIndices.push() = fPositions.count() - 3; - *fIndices.push() = fPositions.count() - 1; - *fIndices.push() = fPositions.count() - 2; + *fIndices.push() = fPositions.count() - 3; + *fIndices.push() = fPositions.count() - 1; + *fIndices.push() = fPositions.count() - 2; + } else { + *fIndices.push() = fPrevUmbraIndex; + *fIndices.push() = fPositions.count() - 2; + *fIndices.push() = fPositions.count() - 1; + + *fIndices.push() = fPrevUmbraIndex; + *fIndices.push() = fPositions.count() - 1; + *fIndices.push() = fPositions.count() - 3; + } // if transparent, add point to first one in array and add to center fan if (fTransparent) { @@ -454,7 +654,7 @@ public: private: void computeClipAndPathPolygons(const SkPath& path, const SkMatrix& ctm, - SkScalar scale, const SkVector& xlate); + const SkMatrix& shadowTransform); void computeClipVectorsAndTestCentroid(); bool clipUmbraPoint(const SkPoint& umbraPoint, const SkPoint& centroid, SkPoint* clipPoint); int getClosestUmbraPoint(const SkPoint& point); @@ -464,7 +664,16 @@ private: void mapPoints(SkScalar scale, const SkVector& xlate, SkPoint* pts, int count); bool addInnerPoint(const SkPoint& pathPoint); - void addEdge(const SkVector& nextPoint, const SkVector& nextNormal) override; + void addEdge(const SkVector& nextPoint, const SkVector& nextNormal); + + SkScalar offset(SkScalar z) { + float zRatio = SkTPin(z / (fLightZ - z), 0.0f, 0.95f); + return fLightRadius*zRatio; + } + + SkScalar fLightZ; + SkScalar fLightRadius; + SkScalar fOffsetAdjust; SkTDArray<SkPoint> fClipPolygon; SkTDArray<SkVector> fClipVectors; @@ -486,27 +695,49 @@ SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMat SkShadowTessellator::HeightFunc heightFunc, const SkPoint3& lightPos, SkScalar lightRadius, SkScalar spotAlpha, bool transparent) - : INHERITED(heightFunc, transparent) - , fCurrClipPoint(0) - , fPrevUmbraOutside(false) - , fFirstUmbraOutside(false) - , fValidUmbra(true) { + : INHERITED(heightFunc, transparent) + , fLightZ(lightPos.fZ) + , fLightRadius(lightRadius) + , fOffsetAdjust(0) + , fCurrClipPoint(0) + , fPrevUmbraOutside(false) + , fFirstUmbraOutside(false) + , fValidUmbra(true) { + + // make sure we're not below the canvas plane + if (this->setZOffset(path.getBounds(), ctm.hasPerspective())) { + // Adjust light height and radius + fLightRadius *= (fLightZ + fZOffset) / fLightZ; + fLightZ += fZOffset; + } // Set radius and colors - // TODO: vary colors and radius based on heightFunc SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY()); - SkScalar occluderHeight = heightFunc(center.fX, center.fY); - float zRatio = SkTPin(occluderHeight / (lightPos.fZ - occluderHeight), 0.0f, 0.95f); + SkScalar occluderHeight = heightFunc(center.fX, center.fY) + fZOffset; + float zRatio = SkTPin(occluderHeight / (fLightZ - occluderHeight), 0.0f, 0.95f); SkScalar radius = lightRadius * zRatio; fRadius = radius; fUmbraColor = SkColorSetARGB(255, 0, spotAlpha * 255.9999f, 255); fPenumbraColor = SkColorSetARGB(255, 0, spotAlpha * 255.9999f, 0); // Compute the scale and translation for the spot shadow. - SkScalar scale = lightPos.fZ / (lightPos.fZ - occluderHeight); - ctm.mapPoints(¢er, 1); - SkVector translate = SkVector::Make(zRatio * (center.fX - lightPos.fX), - zRatio * (center.fY - lightPos.fY)); + SkMatrix shadowTransform; + if (!ctm.hasPerspective()) { + SkScalar scale = fLightZ / (fLightZ - occluderHeight); + SkVector translate = SkVector::Make(-zRatio * lightPos.fX, -zRatio * lightPos.fY); + shadowTransform.setScaleTranslate(scale, scale, translate.fX, translate.fY); + } else { + // For perspective, we have a scale, a z-shear, and another projective divide -- + // this varies at each point so we can't use an affine transform. + // We'll just apply this to each generated point in turn. + shadowTransform.reset(); + // Also can't cull the center (for now). + fTransparent = true; + } + SkMatrix fullTransform = SkMatrix::Concat(shadowTransform, ctm); + + // Set up our reverse mapping + this->setTransformedHeightFunc(fullTransform); // TODO: calculate these reserves better // Penumbra ring: 3*numPts @@ -520,7 +751,7 @@ SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMat fClipPolygon.setReserve(path.countPoints()); // compute rough clip bounds for umbra, plus offset polygon, plus centroid - this->computeClipAndPathPolygons(path, ctm, scale, translate); + this->computeClipAndPathPolygons(path, ctm, shadowTransform); if (fClipPolygon.count() < 3 || fPathPolygon.count() < 3) { return; } @@ -528,6 +759,8 @@ SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMat // check to see if umbra collapses SkScalar minDistSq = fCentroid.distanceToLineSegmentBetweenSqd(fPathPolygon[0], fPathPolygon[1]); + SkRect bounds; + bounds.setBounds(&fPathPolygon[0], fPathPolygon.count()); for (int i = 1; i < fPathPolygon.count(); ++i) { int j = i + 1; if (i == fPathPolygon.count() - 1) { @@ -544,6 +777,7 @@ SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMat if (minDistSq < (radius + kTolerance)*(radius + kTolerance)) { // if the umbra would collapse, we back off a bit on inner blur and adjust the alpha SkScalar newRadius = SkScalarSqrt(minDistSq) - kTolerance; + fOffsetAdjust = newRadius - radius; SkScalar ratio = 256 * newRadius / radius; // they aren't PMColors, but the interpolation algorithm is the same fUmbraColor = SkPMLerp(fUmbraColor, fPenumbraColor, (unsigned)ratio); @@ -554,7 +788,8 @@ SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMat this->computeClipVectorsAndTestCentroid(); // generate inner ring - if (!SkInsetConvexPolygon(&fPathPolygon[0], fPathPolygon.count(), radius, &fUmbraPolygon)) { + if (!SkInsetConvexPolygon(&fPathPolygon[0], fPathPolygon.count(), radius, + &fUmbraPolygon)) { // this shouldn't happen, but just in case we'll inset using the centroid fValidUmbra = false; } @@ -575,15 +810,9 @@ SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMat // finish up the final verts SkVector normal; - if (compute_normal(fPrevPoint, fFirstPoint, fRadius, fDirection, &normal)) { - this->addArc(normal); - - // close out previous arc - *fPositions.push() = fPrevPoint + normal; - *fColors.push() = fPenumbraColor; - *fIndices.push() = fPrevUmbraIndex; - *fIndices.push() = fPositions.count() - 2; - *fIndices.push() = fPositions.count() - 1; + if (compute_normal(fPrevPoint, fFirstPoint, fDirection, &normal)) { + normal *= fRadius; + this->addArc(normal, true); // add to center fan if (fTransparent) { @@ -621,14 +850,15 @@ SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMat *fIndices.push() = fPositions.count() - 2; *fIndices.push() = fPositions.count() - 1; *fIndices.push() = fFirstVertex; + + fPrevNormal = normal; } // final fan if (fPositions.count() >= 3) { fPrevUmbraIndex = fFirstVertex; fPrevPoint = fFirstPoint; - fPrevNormal = normal; - this->addArc(fFirstNormal); + this->addArc(fFirstNormal, false); *fIndices.push() = fFirstVertex; *fIndices.push() = fPositions.count() - 1; @@ -638,19 +868,45 @@ SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMat *fIndices.push() = fFirstVertex + 1; } } + + if (ctm.hasPerspective()) { + for (int i = 0; i < fPositions.count(); ++i) { + SkScalar pathZ = fTransformedHeightFunc(fPositions[i]); + SkScalar factor = SkScalarInvert(fLightZ - pathZ); + fPositions[i].fX = (fPositions[i].fX*fLightZ - lightPos.fX*pathZ)*factor; + fPositions[i].fY = (fPositions[i].fY*fLightZ - lightPos.fY*pathZ)*factor; + } +#ifdef DRAW_CENTROID + SkScalar pathZ = fTransformedHeightFunc(fCentroid); + SkScalar factor = SkScalarInvert(fLightZ - pathZ); + fCentroid.fX = (fCentroid.fX*fLightZ - lightPos.fX*pathZ)*factor; + fCentroid.fY = (fCentroid.fY*fLightZ - lightPos.fY*pathZ)*factor; +#endif + } +#ifdef DRAW_CENTROID + *fPositions.push() = fCentroid + SkVector::Make(-2, -2); + *fColors.push() = SkColorSetARGB(255, 0, 255, 255); + *fPositions.push() = fCentroid + SkVector::Make(2, -2); + *fColors.push() = SkColorSetARGB(255, 0, 255, 255); + *fPositions.push() = fCentroid + SkVector::Make(-2, 2); + *fColors.push() = SkColorSetARGB(255, 0, 255, 255); + *fPositions.push() = fCentroid + SkVector::Make(2, 2); + *fColors.push() = SkColorSetARGB(255, 0, 255, 255); + + *fIndices.push() = fPositions.count() - 4; + *fIndices.push() = fPositions.count() - 2; + *fIndices.push() = fPositions.count() - 1; + + *fIndices.push() = fPositions.count() - 4; + *fIndices.push() = fPositions.count() - 1; + *fIndices.push() = fPositions.count() - 3; +#endif + fSucceeded = true; } void SkSpotShadowTessellator::computeClipAndPathPolygons(const SkPath& path, const SkMatrix& ctm, - SkScalar scale, const SkVector& xlate) { - // For the path polygon we are going to apply 'scale' and 'xlate' (in that order) to each - // computed path point. We want the effect to be to scale the points relative to the path - // bounds center and then translate them by the 'xlate' param we were passed. - SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY()); - ctm.mapPoints(¢er, 1); - SkVector translate = center * (1.f - scale) + xlate; - SkMatrix shadowTransform; - shadowTransform.setScaleTranslate(scale, scale, translate.fX, translate.fY); + const SkMatrix& shadowTransform) { fPathPolygon.setReserve(path.countPoints()); @@ -785,9 +1041,7 @@ bool SkSpotShadowTessellator::clipUmbraPoint(const SkPoint& umbraPoint, const Sk } else if (t_num >= 0 && t_num <= denom) { SkScalar s_num = dp.cross(fClipVectors[fCurrClipPoint]); // if umbra point is inside the clip polygon - if (s_num < 0) { - return false; - } else { + if (s_num >= 0 && s_num <= denom) { segmentVector *= s_num/denom; *clipPoint = umbraPoint + segmentVector; return true; @@ -895,13 +1149,13 @@ void SkSpotShadowTessellator::handlePolyPoint(const SkPoint& p) { fDirection = (perpDot > 0) ? -1 : 1; // add first quad - if (!compute_normal(fInitPoints[0], fInitPoints[1], fRadius, fDirection, - &fFirstNormal)) { + if (!compute_normal(fInitPoints[0], fInitPoints[1], fDirection, &fFirstNormal)) { // first two points are incident, make the third point the second and continue fInitPoints[1] = p; return; } + fFirstNormal *= fRadius; fFirstPoint = fInitPoints[0]; fFirstVertex = fPositions.count(); fPrevNormal = fFirstNormal; @@ -931,9 +1185,10 @@ void SkSpotShadowTessellator::handlePolyPoint(const SkPoint& p) { } SkVector normal; - if (compute_normal(fPrevPoint, p, fRadius, fDirection, &normal)) { - this->addArc(normal); - this->finishArcAndAddEdge(p, normal); + if (compute_normal(fPrevPoint, p, fDirection, &normal)) { + normal *= fRadius; + this->addArc(normal, true); + this->addEdge(p, normal); } } diff --git a/tests/InsetConvexPolyTest.cpp b/tests/InsetConvexPolyTest.cpp index 5376789943..9c1349c8ec 100644 --- a/tests/InsetConvexPolyTest.cpp +++ b/tests/InsetConvexPolyTest.cpp @@ -76,22 +76,20 @@ DEF_TEST(InsetConvexPoly, reporter) { } // just to full inset - // for shadows having a flat poly here is fine - // may want to revisit for strokes + // fails, but outputs a line segment result = SkInsetConvexPolygon(&rrectPoly[0], rrectPoly.count(), 55, &insetPoly); - REPORTER_ASSERT(reporter, result); - REPORTER_ASSERT(reporter, is_convex(insetPoly)); - REPORTER_ASSERT(reporter, insetPoly.count() == 4); - if (insetPoly.count() == 4) { + REPORTER_ASSERT(reporter, !result); + REPORTER_ASSERT(reporter, !is_convex(insetPoly)); + REPORTER_ASSERT(reporter, insetPoly.count() == 2); + if (insetPoly.count() == 2) { REPORTER_ASSERT(reporter, insetPoly[0].equals(-50, 0)); REPORTER_ASSERT(reporter, insetPoly[1].equals(50, 0)); - REPORTER_ASSERT(reporter, insetPoly[2].equals(50, 0)); - REPORTER_ASSERT(reporter, insetPoly[3].equals(-50, 0)); } // past full inset result = SkInsetConvexPolygon(&rrectPoly[0], rrectPoly.count(), 75, &insetPoly); REPORTER_ASSERT(reporter, !result); + REPORTER_ASSERT(reporter, insetPoly.count() == 0); // troublesome case SkTDArray<SkPoint> clippedRRectPoly; |