aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--include/utils/SkCamera.h8
-rw-r--r--samplecode/SampleAndroidShadows.cpp74
-rw-r--r--src/utils/SkCamera.cpp24
-rwxr-xr-xsrc/utils/SkInsetConvexPolygon.cpp77
-rwxr-xr-xsrc/utils/SkInsetConvexPolygon.h35
-rwxr-xr-x[-rw-r--r--]src/utils/SkShadowTessellator.cpp471
-rw-r--r--tests/InsetConvexPolyTest.cpp14
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(&center, 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(&center, 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;