Some more tweaks to perspective path clipping

Bug: skia:9546
Change-Id: Ib91880e09ae6373bc133d684aa7510c67681ec1e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/257629
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
diff --git a/samplecode/SampleClip.cpp b/samplecode/SampleClip.cpp
index 0e2546e..37cb7ec 100644
--- a/samplecode/SampleClip.cpp
+++ b/samplecode/SampleClip.cpp
@@ -146,15 +146,30 @@
     }
 
     bool twoPts(SkPoint pts[2]) const {
-        if (fB) {
-            pts[0] = { 0, -fC / fB };
-            pts[1] = { 1, (-fC - fA) / fB };
-        } else if (fA) {
-            pts[0] = { -fC / fA,        0 };
-            pts[1] = { (-fC - fB) / fA, 1 };
+        // normalize plane to help with the perpendicular step, below
+        SkScalar len = SkScalarSqrt(fA*fA + fB*fB);
+        if (!len) {
+            return false;
+        }
+        SkScalar denom = SkScalarInvert(len);
+        SkScalar a = fA * denom;
+        SkScalar b = fB * denom;
+        SkScalar c = fC * denom;
+
+        // We compute p0 on the half-plane by setting one of the components to 0
+        // We compute p1 by stepping from p0 along a perpendicular to the normal
+        if (b) {
+            pts[0] = { 0, -c / b };
+            pts[1] = { b, pts[0].fY - a};
+        } else if (a) {
+            pts[0] = { -c / a,        0 };
+            pts[1] = { pts[0].fX + b, -a };
         } else {
             return false;
         }
+
+        SkASSERT(SkScalarNearlyZero(this->operator()(pts[0].fX, pts[0].fY)));
+        SkASSERT(SkScalarNearlyZero(this->operator()(pts[1].fX, pts[1].fY)));
         return true;
     }
 };
@@ -222,6 +237,39 @@
 }
 
 static SkPath clip(const SkPath& path, const SkHalfPlane& plane) {
+    // do a quick bounds check to see if we need to clip at all
+    const SkRect& bounds = path.getBounds();
+
+    // check whether the diagonal aligned with the normal crosses the plane
+    SkPoint diagMin, diagMax;
+    if (plane.fA >= 0) {
+        diagMin.fX = bounds.fLeft;
+        diagMax.fX = bounds.fRight;
+    } else {
+        diagMin.fX = bounds.fRight;
+        diagMax.fX = bounds.fLeft;
+    }
+    if (plane.fB >= 0) {
+        diagMin.fY = bounds.fTop;
+        diagMax.fY = bounds.fBottom;
+    } else {
+        diagMin.fY = bounds.fBottom;
+        diagMax.fY = bounds.fTop;
+    }
+    SkScalar test = plane(diagMin.fX, diagMin.fY);
+    SkScalar sign = test*plane(diagMax.fX, diagMin.fY);
+    if (sign > 0) {
+        // the path is either all on one side of the half-plane or the other
+        if (test < 0) {
+            // completely culled
+            return SkPath();
+        } else {
+            // no clipping necessary
+            return path;
+        }
+    }
+
+    // quick check failed, we have to clip
     SkPoint pts[2];
     if (plane.twoPts(pts)) {
         return clip(path, pts[0], pts[1]);
@@ -393,7 +441,7 @@
 
     a = 2*a/W - m;  b = 2*b/W - n;  d = 2*d/W - p;
     e = 2*e/H - m;  f = 2*f/H - n;  h = 2*h/H - p;
-    i = 2*i   - m;  j = 2*j   - n;  l = 2*l   - p;
+//    i = 2*i   - m;  j = 2*j   - n;  l = 2*l   - p;
 
     planes[0] = { m - a, n - b, p - d }; // w - x
     planes[1] = { m + a, n + b, p + d }; // w + x
@@ -403,6 +451,19 @@
     planes[5] = { m + i, n + j, p + l }; // w + z
 }
 
+static SkHalfPlane half_plane_w0(const SkMatrix44& m44, SkScalar W, SkScalar H) {
+    float mx[16];
+    m44.asColMajorf(mx);
+
+    SkScalar
+//            a = mx[0], b = mx[4], /* c = mx[ 8], */ d = mx[12],
+//             e = mx[1], f = mx[5], /* g = mx[ 9], */ h = mx[13],
+//             i = mx[2], j = mx[6], /* k = mx[10], */ l = mx[14],
+             m = mx[3], n = mx[7], /* o = mx[11], */ p = mx[15];
+
+    return { m, n, p - 0.05f };  // w = 0.05f
+}
+
 class HalfPlaneView3 : public Sample {
     float   fNear = 0.05f;
     float   fFar = 4;
@@ -413,6 +474,7 @@
     SkPoint3    fUp  { 0, 1, 0 };
 
     SkMatrix44  fRot;
+    SkPoint3    fTrans;
 
     SkPath fPath;
     sk_sp<SkShader> fShader;
@@ -440,13 +502,15 @@
     SkMatrix44 get44() const {
         SkMatrix44  camera,
                     perspective,
+                    translate,
                     viewport;
 
         Sk3Perspective(&perspective, fNear, fFar, fAngle);
         Sk3LookAt(&camera, fEye, fCOA, fUp);
+        translate.setTranslate(fTrans.fX, fTrans.fY, fTrans.fZ);
         viewport.setScale(200, 200, 1).postTranslate( 200,  200, 0);
 
-        return viewport * perspective * camera * fRot * inv(viewport);
+        return viewport * perspective * camera * translate * fRot * inv(viewport);
     }
 
     void onDrawContent(SkCanvas* canvas) override {
@@ -468,8 +532,9 @@
 
         SkHalfPlane planes[6];
         half_planes(mx44, 400, 400, planes);
+        SkHalfPlane hpw = half_plane_w0(mx44, 400, 400);
 
-        SkPath path = clip(fPath, planes[4]);
+        SkPath path = clip(fPath, hpw);//planes[4]);
         canvas->save();
         canvas->concat(mx);
         canvas->drawPath(path, paint);
@@ -479,7 +544,8 @@
 //            draw_halfplane(canvas, p, SK_ColorRED);
 //        }
         draw_halfplane(canvas, planes[4], SK_ColorBLUE);
-        draw_halfplane(canvas, planes[5], SK_ColorGREEN);
+//        draw_halfplane(canvas, planes[5], SK_ColorGREEN);
+        draw_halfplane(canvas, hpw, SK_ColorRED);
     }
 
     bool onChar(SkUnichar uni) override {
@@ -492,8 +558,8 @@
             case '-': this->rotate(0, 0,  delta); return true;
             case '+': this->rotate(0, 0, -delta); return true;
 
-            case 'i': fEye.fZ += 0.1f; SkDebugf("ez %g\n", fEye.fZ); return true;
-            case 'k': fEye.fZ -= 0.1f; SkDebugf("ez %g\n", fEye.fZ); return true;
+            case 'i': fTrans.fZ += 0.1f; SkDebugf("ez %g\n", fTrans.fZ); return true;
+            case 'k': fTrans.fZ -= 0.1f; SkDebugf("ez %g\n", fTrans.fZ); return true;
 
             case 'n': fNear += 0.1f; SkDebugf("near %g\n", fNear); return true;
             case 'N': fNear -= 0.1f; SkDebugf("near %g\n", fNear); return true;