From 190b82d67ce1ce29cf2c3fb148882711497e3229 Mon Sep 17 00:00:00 2001 From: Mike Reed Date: Tue, 17 Dec 2019 17:25:27 -0500 Subject: [PATCH] Add clipping for perspective triangles more UI for halfplanes in SampleClip bug: skia:9698 Change-Id: I9463fe9860fa482ef05fc2113114e61524c38fc0 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/260500 Commit-Queue: Mike Reed Reviewed-by: Robert Phillips --- include/core/SkMatrix.h | 5 ++ include/core/SkPoint3.h | 12 +-- samplecode/SampleClip.cpp | 68 ++++++++++++++++- src/core/SkDraw_vertices.cpp | 142 ++++++++++++++++++++++++++++++----- src/core/SkMatrix.cpp | 24 ++++++ tests/MatrixTest.cpp | 2 +- 6 files changed, 225 insertions(+), 28 deletions(-) diff --git a/include/core/SkMatrix.h b/include/core/SkMatrix.h index 463257dc20..598e15fff4 100644 --- a/include/core/SkMatrix.h +++ b/include/core/SkMatrix.h @@ -1313,6 +1313,11 @@ public: */ void mapHomogeneousPoints(SkPoint3 dst[], const SkPoint3 src[], int count) const; + /** + * Returns homogeneous points, starting with 2D src points (with implied w = 1). + */ + void mapHomogeneousPoints(SkPoint3 dst[], const SkPoint src[], int count) const; + /** Maps SkPoint (x, y) to result. SkPoint is mapped by multiplying by SkMatrix. Given: | A B C | | x | diff --git a/include/core/SkPoint3.h b/include/core/SkPoint3.h index 15b082a427..e372f82791 100644 --- a/include/core/SkPoint3.h +++ b/include/core/SkPoint3.h @@ -78,17 +78,13 @@ struct SK_API SkPoint3 { a and b (i.e., a - b) */ friend SkPoint3 operator-(const SkPoint3& a, const SkPoint3& b) { - SkPoint3 v; - v.set(a.fX - b.fX, a.fY - b.fY, a.fZ - b.fZ); - return v; + return { a.fX - b.fX, a.fY - b.fY, a.fZ - b.fZ }; } /** Returns a new point whose coordinates are the sum of a and b (a + b) */ friend SkPoint3 operator+(const SkPoint3& a, const SkPoint3& b) { - SkPoint3 v; - v.set(a.fX + b.fX, a.fY + b.fY, a.fZ + b.fZ); - return v; + return { a.fX + b.fX, a.fY + b.fY, a.fZ + b.fZ }; } /** Add v's coordinates to the point's @@ -107,6 +103,10 @@ struct SK_API SkPoint3 { fZ -= v.fZ; } + friend SkPoint3 operator*(SkScalar t, SkPoint3 p) { + return { t * p.fX, t * p.fY, t * p.fZ }; + } + /** Returns true if fX, fY, and fZ are measurable values. @return true for values other than infinities and NaN diff --git a/samplecode/SampleClip.cpp b/samplecode/SampleClip.cpp index ad1b917464..5ee9827cde 100644 --- a/samplecode/SampleClip.cpp +++ b/samplecode/SampleClip.cpp @@ -584,7 +584,13 @@ DEF_SAMPLE( return new HalfPlaneView3(); ) class HalfPlaneCoons : public SampleCameraView { SkPoint fPatch[12]; - SkColor fColors[4] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorBLACK }; + SkColor fColors[4] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorBLACK }; + SkPoint fTex[4] = {{0, 0}, {256, 0}, {256, 256}, {0, 256}}; + sk_sp fShader; + + bool fShowHandles = false; + bool fShowSkeleton = false; + bool fShowTex = false; SkString name() override { return SkString("halfplane-coons"); } @@ -601,6 +607,8 @@ class HalfPlaneCoons : public SampleCameraView { fPatch[9] = { 0, 300 }; fPatch[10] = { 0, 200 }; fPatch[11] = { 0, 100 }; + + fShader = GetResourceAsImage("images/mandrill_256.png")->makeShader(); } void onDrawContent(SkCanvas* canvas) override { @@ -610,8 +618,64 @@ class HalfPlaneCoons : public SampleCameraView { canvas->save(); canvas->concat(mx); - canvas->drawPatch(fPatch, fColors, nullptr, SkBlendMode::kSrc, paint); + + const SkPoint* tex = nullptr; + const SkColor* col = nullptr; + if (!fShowSkeleton) { + if (fShowTex) { + paint.setShader(fShader); + tex = fTex; + } else { + col = fColors; + } + } + canvas->drawPatch(fPatch, col, tex, SkBlendMode::kSrc, paint); + paint.setShader(nullptr); + + if (fShowHandles) { + paint.setAntiAlias(true); + paint.setStrokeCap(SkPaint::kRound_Cap); + paint.setStrokeWidth(8); + canvas->drawPoints(SkCanvas::kPoints_PointMode, 12, fPatch, paint); + paint.setColor(SK_ColorWHITE); + paint.setStrokeWidth(6); + canvas->drawPoints(SkCanvas::kPoints_PointMode, 12, fPatch, paint); + } + canvas->restore(); } + + Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override { + auto dist = [](SkPoint a, SkPoint b) { return (b - a).length(); }; + + const float tol = 15; + for (int i = 0; i < 12; ++i) { + if (dist({x,y}, fPatch[i]) <= tol) { + Click* c = new Click; + c->fMeta.setS32("index", i); + return c; + } + } + return nullptr; + } + + bool onClick(Click* click) override { + int32_t index; + SkAssertResult(click->fMeta.findS32("index", &index)); + SkASSERT(index >= 0 && index < 12); + fPatch[index] = click->fCurr; + return true; + } + + bool onChar(SkUnichar uni) override { + switch (uni) { + case 'h': fShowHandles = !fShowHandles; return true; + case 's': fShowSkeleton = !fShowSkeleton; return true; + case 't': fShowTex = !fShowTex; return true; + default: break; + } + return this->SampleCameraView::onChar(uni); + } + }; DEF_SAMPLE( return new HalfPlaneCoons(); ) diff --git a/src/core/SkDraw_vertices.cpp b/src/core/SkDraw_vertices.cpp index 14b3e13418..19a854de55 100644 --- a/src/core/SkDraw_vertices.cpp +++ b/src/core/SkDraw_vertices.cpp @@ -19,6 +19,62 @@ #include "src/shaders/SkComposeShader.h" #include "src/shaders/SkShaderBase.h" +// Compute the crossing point (across zero) for the two values, expressed as a +// normalized 0...1 value. If curr is 0, returns 0. If next is 0, returns 1. +// +static float compute_t(float curr, float next) { + SkASSERT((curr > 0 && next <= 0) || (curr <= 0 && next > 0)); + float t = curr / (curr - next); + SkASSERT(t >= 0 && t <= 1); + return t; +} + +static SkPoint3 lerp(SkPoint3 curr, SkPoint3 next, float t) { + return curr + t * (next - curr); +} + +// tol is the nudge away from zero, to keep the numerics nice. +// Think of it as our near-clipping-plane (or w-plane). +static SkPoint3 clip(SkPoint3 curr, SkPoint3 next, float tol) { + // Return the point between curr and next where the fZ value corses tol. + // To be (really) perspective correct, we should be computing baesd on 1/Z, not Z. + // For now, this is close enough (and faster). + return lerp(curr, next, compute_t(curr.fZ - tol, next.fZ - tol)); +} + +constexpr int kMaxClippedTrianglePointCount = 4; +// Clip a triangle (based on its homogeneous W values), and return the projected polygon. +// Since we only clip against one "edge"/plane, the max number of points in the clipped +// polygon is 4. +static int clip_triangle(SkPoint dst[], const int idx[3], const SkPoint3 pts[]) { + SkPoint3 outPoints[4]; + SkPoint3* outP = outPoints; + const float tol = 0.05f; + + for (int i = 0; i < 3; ++i) { + int curr = idx[i]; + int next = idx[(i + 1) % 3]; + if (pts[curr].fZ > tol) { + *outP++ = pts[curr]; + if (pts[next].fZ <= tol) { // curr is IN, next is OUT + *outP++ = clip(pts[curr], pts[next], tol); + } + } else { + if (pts[next].fZ > tol) { // curr is OUT, next is IN + *outP++ = clip(pts[curr], pts[next], tol); + } + } + } + + const int count = outP - outPoints; + SkASSERT(count == 0 || count == 3 || count == 4); + for (int i = 0; i < count; ++i) { + float scale = 1.0f / outPoints[i].fZ; + dst[i].set(outPoints[i].fX * scale, outPoints[i].fY * scale); + } + return count; +} + struct Matrix43 { float fMat[12]; // column major @@ -260,10 +316,16 @@ void SkDraw::drawVertices(SkVertices::VertexMode vmode, int vertexCount, vertices = deformed; } - SkPoint* devVerts = outerAlloc.makeArray(vertexCount); - fMatrix->mapPoints(devVerts, vertices, vertexCount); + SkPoint* devVerts = nullptr; + SkPoint3* dev3 = nullptr; + + if (fMatrix->hasPerspective()) { + dev3 = outerAlloc.makeArray(vertexCount); + fMatrix->mapHomogeneousPoints(dev3, vertices, vertexCount); + } else { + devVerts = outerAlloc.makeArray(vertexCount); + fMatrix->mapPoints(devVerts, vertices, vertexCount); - { SkRect bounds; // this also sets bounds to empty if we see a non-finite value bounds.setBounds(devVerts, vertexCount); @@ -275,6 +337,7 @@ void SkDraw::drawVertices(SkVertices::VertexMode vmode, int vertexCount, VertState state(vertexCount, indices, indexCount); VertState::Proc vertProc = state.chooseProc(vmode); + // Draw hairlines to show the skeleton if (!(colors || textures)) { // no colors[] and no texture, stroke hairlines with paint's color. SkPaint p; @@ -287,10 +350,26 @@ void SkDraw::drawVertices(SkVertices::VertexMode vmode, int vertexCount, SkScan::HairRCProc hairProc = ChooseHairProc(paint.isAntiAlias()); const SkRasterClip& clip = *fRC; while (vertProc(&state)) { - SkPoint array[] = { - devVerts[state.f0], devVerts[state.f1], devVerts[state.f2], devVerts[state.f0] - }; - hairProc(array, 4, clip, blitter.get()); + if (dev3) { + SkPoint tmp[kMaxClippedTrianglePointCount + 2]; + int idx[] = { state.f0, state.f1, state.f2 }; + if (int n = clip_triangle(tmp, idx, dev3)) { + tmp[n] = tmp[0]; // close the poly + if (n == 3) { + n = 4; + } else { + SkASSERT(n == 4); + tmp[5] = tmp[2]; // add diagonal + n = 6; + } + hairProc(tmp, n, clip, blitter.get()); + } + } else { + SkPoint array[] = { + devVerts[state.f0], devVerts[state.f1], devVerts[state.f2], devVerts[state.f0] + }; + hairProc(array, 4, clip, blitter.get()); + } } return; } @@ -310,6 +389,28 @@ void SkDraw::drawVertices(SkVertices::VertexMode vmode, int vertexCount, } } + auto handle_devVerts = [&](SkBlitter* blitter) { + SkPoint tmp[] = { + devVerts[state.f0], devVerts[state.f1], devVerts[state.f2] + }; + SkScan::FillTriangle(tmp, *fRC, blitter); + }; + + auto handle_dev3 = [&](SkBlitter* blitter) { + SkPoint tmp[kMaxClippedTrianglePointCount]; + int idx[] = { state.f0, state.f1, state.f2 }; + if (int n = clip_triangle(tmp, idx, dev3)) { + // TODO: SkScan::FillConvexPoly(tmp, n, ...); + SkASSERT(n == 3 || n == 4); + SkScan::FillTriangle(tmp, *fRC, blitter); + if (n == 4) { + tmp[1] = tmp[2]; + tmp[2] = tmp[3]; + SkScan::FillTriangle(tmp, *fRC, blitter); + } + } + }; + SkPaint p(paint); p.setShader(sk_ref_sp(shader)); @@ -320,10 +421,11 @@ void SkDraw::drawVertices(SkVertices::VertexMode vmode, int vertexCount, continue; } - SkPoint tmp[] = { - devVerts[state.f0], devVerts[state.f1], devVerts[state.f2] - }; - SkScan::FillTriangle(tmp, *fRC, blitter); + if (dev3) { + handle_dev3(blitter); + } else { + handle_devVerts(blitter); + } } return; } @@ -352,10 +454,11 @@ void SkDraw::drawVertices(SkVertices::VertexMode vmode, int vertexCount, continue; } - SkPoint tmp[] = { - devVerts[state.f0], devVerts[state.f1], devVerts[state.f2] - }; - SkScan::FillTriangle(tmp, *fRC, blitter); + if (dev3) { + handle_dev3(blitter); + } else { + handle_devVerts(blitter); + } } } else { // must rebuild pipeline for each triangle, to pass in the computed ctm @@ -378,11 +481,12 @@ void SkDraw::drawVertices(SkVertices::VertexMode vmode, int vertexCount, ctm = &tmpCtm; } - SkPoint tmp[] = { - devVerts[state.f0], devVerts[state.f1], devVerts[state.f2] - }; auto blitter = SkCreateRasterPipelineBlitter(fDst, p, *ctm, &innerAlloc); - SkScan::FillTriangle(tmp, *fRC, blitter); + if (dev3) { + handle_dev3(blitter); + } else { + handle_devVerts(blitter); + } } } } diff --git a/src/core/SkMatrix.cpp b/src/core/SkMatrix.cpp index e992fa5743..fdbf991664 100644 --- a/src/core/SkMatrix.cpp +++ b/src/core/SkMatrix.cpp @@ -1080,6 +1080,30 @@ void SkMatrix::mapHomogeneousPoints(SkPoint3 dst[], const SkPoint3 src[], int co sizeof(SkPoint3), count); } +void SkMatrix::mapHomogeneousPoints(SkPoint3 dst[], const SkPoint src[], int count) const { + if (this->isIdentity()) { + for (int i = 0; i < count; ++i) { + dst[i] = { src[i].fX, src[i].fY, 1 }; + } + } else if (this->hasPerspective()) { + for (int i = 0; i < count; ++i) { + dst[i] = { + fMat[0] * src[i].fX + fMat[1] * src[i].fY + fMat[2], + fMat[3] * src[i].fX + fMat[4] * src[i].fY + fMat[5], + fMat[6] * src[i].fX + fMat[7] * src[i].fY + fMat[8], + }; + } + } else { // affine + for (int i = 0; i < count; ++i) { + dst[i] = { + fMat[0] * src[i].fX + fMat[1] * src[i].fY + fMat[2], + fMat[3] * src[i].fX + fMat[4] * src[i].fY + fMat[5], + 1, + }; + } + } +} + /////////////////////////////////////////////////////////////////////////////// void SkMatrix::mapVectors(SkPoint dst[], const SkPoint src[], int count) const { diff --git a/tests/MatrixTest.cpp b/tests/MatrixTest.cpp index ced094ac38..e09d597cf2 100644 --- a/tests/MatrixTest.cpp +++ b/tests/MatrixTest.cpp @@ -699,7 +699,7 @@ static void test_matrix_homogeneous(skiatest::Reporter* reporter) { // doesn't crash with null dst, src, count == 0 { - mats[0].mapHomogeneousPoints(nullptr, nullptr, 0); + mats[0].mapHomogeneousPoints(nullptr, (const SkPoint3*)nullptr, 0); } // uniform scale of point