Expose camera matrix in SkCanvas

3 new getters:
- localToWorld
- localToCamera
- localToDevice (same as total-matrix)

The current tracking minimizes overhead, by using a computed inverse to
produce the localToWorld/Camera. This can be change as needed in the
future (more precision, but more memory/overhead), but for now is
sufficient to try out the new APIs.

Change-Id: I85440318f36dca935124b782e110fe9c0152ae7a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/264648
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
diff --git a/samplecode/Sample3D.cpp b/samplecode/Sample3D.cpp
index d52154e..828cf99 100644
--- a/samplecode/Sample3D.cpp
+++ b/samplecode/Sample3D.cpp
@@ -144,9 +144,71 @@
     {             0,-SK_ScalarPI/2 }, // right
 };
 
+#include "include/core/SkColorFilter.h"
+#include "include/effects/SkColorMatrix.h"
+
+static SkV3 normalize(SkV3 v) { return v * (1.0f / v.length()); }
+
+static SkColorMatrix comput_planar_lighting(SkCanvas* canvas, SkV3 lightDir) {
+    SkM44 l2w = canvas->getLocalToWorld();
+    auto normal = normalize(l2w * SkV3{0, 0, 1});
+    float dot = -normal * lightDir;
+
+    SkColorMatrix cm;
+    if (dot < 0) {
+        dot = 0;
+    }
+
+    float ambient = 0.5f;
+    float scale = ambient + dot;
+    cm.setScale(scale, scale, scale, 1);
+    return cm;
+}
+
+struct Light {
+    SkPoint fCenter;
+    SkPoint fEndPt;
+    SkScalar fRadius;
+    SkScalar fHeight;
+
+    bool hitTest(SkScalar x, SkScalar y) const {
+        auto xx = x - fCenter.fX;
+        auto yy = y - fCenter.fY;
+        return xx*xx + yy*yy <= fRadius*fRadius;
+    }
+
+    void update(SkScalar x, SkScalar y) {
+        auto xx = x - fCenter.fX;
+        auto yy = y - fCenter.fY;
+        auto len = SkScalarSqrt(xx*xx + yy*yy);
+        if (len > fRadius) {
+            xx *= fRadius / len;
+            yy *= fRadius / len;
+        }
+        fEndPt = {fCenter.fX + xx, fCenter.fY + yy};
+    }
+
+    SkV3 getDir() const {
+        auto pt = fEndPt - fCenter;
+        return normalize({pt.fX, pt.fY, -fHeight});
+    }
+
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        canvas->drawCircle(fCenter.fX, fCenter.fY, 5, paint);
+        paint.setStyle(SkPaint::kStroke_Style);
+        canvas->drawCircle(fCenter.fX, fCenter.fY, fRadius, paint);
+        paint.setColor(SK_ColorRED);
+        canvas->drawLine(fCenter.fX, fCenter.fY, fEndPt.fX, fEndPt.fY, paint);
+    }
+};
 
 class SampleRR3D : public Sample3DView {
     SkRRect fRR;
+    Light   fLight = {
+        {60, 60}, {60, 60}, 50, 10
+    };
     sk_sp<SkShader> fShader;
 
     SkString name() override { return SkString("rrect3d"); }
@@ -167,13 +229,22 @@
 
         canvas->concat(trans * fRot * m * inv(trans));
 
+        if (!front(canvas->getLocalToDevice())) {
+            return;
+        }
+
         SkPaint paint;
-        paint.setAlphaf(front(canvas->getTotalM44()) ? 1 : 0.25f);
+        paint.setAlphaf(front(canvas->getLocalToDevice()) ? 1 : 0.25f);
         paint.setShader(fShader);
+
+        SkColorMatrix cm = comput_planar_lighting(canvas, fLight.getDir());
+        paint.setColorFilter(SkColorFilters::Matrix(cm));
+
         canvas->drawRRect(fRR, paint);
     }
 
     void onDrawContent(SkCanvas* canvas) override {
+        canvas->save();
         canvas->translate(400, 300);
 
         this->saveCamera(canvas, {0, 0, 400, 400}, 200);
@@ -184,6 +255,20 @@
         }
 
         canvas->restore();
+        canvas->restore();
+
+        fLight.draw(canvas);
+    }
+
+    Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
+        if (fLight.hitTest(x, y)) {
+            return new Click();
+        }
+        return nullptr;
+    }
+    bool onClick(Click* click) override {
+        fLight.update(click->fCurr.fX, click->fCurr.fY);
+        return true;
     }
 };
 DEF_SAMPLE( return new SampleRR3D(); )