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 <reed@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Mike Reed 2019-12-17 17:25:27 -05:00 committed by Skia Commit-Bot
parent 3f1a98b779
commit 190b82d67c
6 changed files with 225 additions and 28 deletions

View File

@ -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 |

View File

@ -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

View File

@ -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<SkShader> 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(); )

View File

@ -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<SkPoint>(vertexCount);
fMatrix->mapPoints(devVerts, vertices, vertexCount);
SkPoint* devVerts = nullptr;
SkPoint3* dev3 = nullptr;
if (fMatrix->hasPerspective()) {
dev3 = outerAlloc.makeArray<SkPoint3>(vertexCount);
fMatrix->mapHomogeneousPoints(dev3, vertices, vertexCount);
} else {
devVerts = outerAlloc.makeArray<SkPoint>(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);
}
}
}
}

View File

@ -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 {

View File

@ -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