1 /*
2  * Copyright 2020 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "include/core/SkCanvas.h"
9 #include "include/core/SkM44.h"
10 #include "include/core/SkPaint.h"
11 #include "include/core/SkRRect.h"
12 #include "include/utils/SkRandom.h"
13 #include "samplecode/Sample.h"
14 #include "tools/Resources.h"
15 
16 struct VSphere {
17     SkV2     fCenter;
18     SkScalar fRadius;
19 
VSphereVSphere20     VSphere(SkV2 center, SkScalar radius) : fCenter(center), fRadius(radius) {}
21 
containsVSphere22     bool contains(SkV2 v) const {
23         return (v - fCenter).length() <= fRadius;
24     }
25 
pinLocVSphere26     SkV2 pinLoc(SkV2 p) const {
27         auto v = p - fCenter;
28         if (v.length() > fRadius) {
29             v *= (fRadius / v.length());
30         }
31         return fCenter + v;
32     }
33 
computeUnitV3VSphere34     SkV3 computeUnitV3(SkV2 v) const {
35         v = (v - fCenter) * (1 / fRadius);
36         SkScalar len2 = v.lengthSquared();
37         if (len2 > 1) {
38             v = v.normalize();
39             len2 = 1;
40         }
41         SkScalar z = SkScalarSqrt(1 - len2);
42         return {v.x, v.y, z};
43     }
44 
45     struct RotateInfo {
46         SkV3    fAxis;
47         SkScalar fAngle;
48     };
49 
computeRotationInfoVSphere50     RotateInfo computeRotationInfo(SkV2 a, SkV2 b) const {
51         SkV3 u = this->computeUnitV3(a);
52         SkV3 v = this->computeUnitV3(b);
53         SkV3 axis = u.cross(v);
54         SkScalar length = axis.length();
55 
56         if (!SkScalarNearlyZero(length)) {
57             return {axis * (1.0f / length), acos(u.dot(v))};
58         }
59         return {{0, 0, 0}, 0};
60     }
61 
computeRotationVSphere62     SkM44 computeRotation(SkV2 a, SkV2 b) const {
63         auto [axis, angle] = this->computeRotationInfo(a, b);
64         return SkM44::Rotate(axis, angle);
65     }
66 };
67 
inv(const SkM44 & m)68 static SkM44 inv(const SkM44& m) {
69     SkM44 inverse;
70     SkAssertResult(m.invert(&inverse));
71     return inverse;
72 }
73 
74 class Sample3DView : public Sample {
75 protected:
76     float   fNear = 0.05f;
77     float   fFar = 4;
78     float   fAngle = SK_ScalarPI / 12;
79 
80     SkV3    fEye { 0, 0, 1.0f/tan(fAngle/2) - 1 };
81     SkV3    fCOA { 0, 0, 0 };
82     SkV3    fUp  { 0, 1, 0 };
83 
84 public:
saveCamera(SkCanvas * canvas,const SkRect & area,SkScalar zscale)85     void saveCamera(SkCanvas* canvas, const SkRect& area, SkScalar zscale) {
86         SkM44 camera = Sk3LookAt(fEye, fCOA, fUp),
87               perspective = Sk3Perspective(fNear, fFar, fAngle),
88               viewport = SkM44::Translate(area.centerX(), area.centerY(), 0) *
89                          SkM44::Scale(area.width()*0.5f, area.height()*0.5f, zscale);
90 
91         // want "world" to be in our big coordinates (e.g. area), so apply this inverse
92         // as part of our "camera".
93         canvas->experimental_saveCamera(viewport * perspective, camera * inv(viewport));
94     }
95 };
96 
97 struct Face {
98     SkScalar fRx, fRy;
99     SkColor  fColor;
100 
TFace101     static SkM44 T(SkScalar x, SkScalar y, SkScalar z) {
102         return SkM44::Translate(x, y, z);
103     }
104 
RFace105     static SkM44 R(SkV3 axis, SkScalar rad) {
106         return SkM44::Rotate(axis, rad);
107     }
108 
asM44Face109     SkM44 asM44(SkScalar scale) const {
110         return R({0,1,0}, fRy) * R({1,0,0}, fRx) * T(0, 0, scale);
111     }
112 };
113 
front(const SkM44 & m)114 static bool front(const SkM44& m) {
115     SkM44 m2(SkM44::kUninitialized_Constructor);
116     if (!m.invert(&m2)) {
117         m2.setIdentity();
118     }
119     /*
120      *  Classically we want to dot the transpose(inverse(ctm)) with our surface normal.
121      *  In this case, the normal is known to be {0, 0, 1}, so we only actually need to look
122      *  at the z-scale of the inverse (the transpose doesn't change the main diagonal, so
123      *  no need to actually transpose).
124      */
125     return m2.rc(2,2) > 0;
126 }
127 
128 const Face faces[] = {
129     {             0,             0,  SK_ColorRED }, // front
130     {             0,   SK_ScalarPI,  SK_ColorGREEN }, // back
131 
132     { SK_ScalarPI/2,             0,  SK_ColorBLUE }, // top
133     {-SK_ScalarPI/2,             0,  SK_ColorCYAN }, // bottom
134 
135     {             0, SK_ScalarPI/2,  SK_ColorMAGENTA }, // left
136     {             0,-SK_ScalarPI/2,  SK_ColorYELLOW }, // right
137 };
138 
139 #include "include/effects/SkRuntimeEffect.h"
140 
141 struct LightOnSphere {
142     SkV2     fLoc;
143     SkScalar fDistance;
144     SkScalar fRadius;
145 
computeWorldPosLightOnSphere146     SkV3 computeWorldPos(const VSphere& s) const {
147         return s.computeUnitV3(fLoc) * fDistance;
148     }
149 
drawLightOnSphere150     void draw(SkCanvas* canvas) const {
151         SkPaint paint;
152         paint.setAntiAlias(true);
153         paint.setColor(SK_ColorWHITE);
154         canvas->drawCircle(fLoc.x, fLoc.y, fRadius + 2, paint);
155         paint.setColor(SK_ColorBLACK);
156         canvas->drawCircle(fLoc.x, fLoc.y, fRadius, paint);
157     }
158 };
159 
160 #include "include/core/SkTime.h"
161 
162 class RotateAnimator {
163     SkV3        fAxis = {0, 0, 0};
164     SkScalar    fAngle = 0,
165                 fPrevAngle = 1234567;
166     double      fNow = 0,
167                 fPrevNow = 0;
168 
169     SkScalar    fAngleSpeed = 0,
170                 fAngleSign = 1;
171 
172     static constexpr double kSlowDown = 4;
173     static constexpr SkScalar kMaxSpeed = 16;
174 
175 public:
update(SkV3 axis,SkScalar angle)176     void update(SkV3 axis, SkScalar angle) {
177         if (angle != fPrevAngle) {
178             fPrevAngle = fAngle;
179             fAngle = angle;
180 
181             fPrevNow = fNow;
182             fNow = SkTime::GetSecs();
183 
184             fAxis = axis;
185         }
186     }
187 
rotation()188     SkM44 rotation() {
189         if (fAngleSpeed > 0) {
190             double now = SkTime::GetSecs();
191             double dtime = now - fPrevNow;
192             fPrevNow = now;
193             double delta = fAngleSign * fAngleSpeed * dtime;
194             fAngle += delta;
195             fAngleSpeed -= kSlowDown * dtime;
196             if (fAngleSpeed < 0) {
197                 fAngleSpeed = 0;
198             }
199         }
200         return SkM44::Rotate(fAxis, fAngle);
201 
202     }
203 
start()204     void start() {
205         if (fPrevNow != fNow) {
206             fAngleSpeed = (fAngle - fPrevAngle) / (fNow - fPrevNow);
207             fAngleSign = fAngleSpeed < 0 ? -1 : 1;
208             fAngleSpeed = std::min(kMaxSpeed, std::abs(fAngleSpeed));
209         } else {
210             fAngleSpeed = 0;
211         }
212         fPrevNow = SkTime::GetSecs();
213         fAngle = 0;
214     }
215 
reset()216     void reset() {
217         fAngleSpeed = 0;
218         fAngle = 0;
219         fPrevAngle = 1234567;
220     }
221 
isAnimating() const222     bool isAnimating() const { return fAngleSpeed != 0; }
223 };
224 
225 class SampleCubeBase : public Sample3DView {
226     enum {
227         DX = 400,
228         DY = 300
229     };
230 
231     SkM44 fWorldToClick,
232           fClickToWorld;
233 
234     SkM44 fRotation;        // part of model
235 
236     RotateAnimator fRotateAnimator;
237 
238 protected:
239     enum Flags {
240         kCanRunOnCPU    = 1 << 0,
241         kShowLightDome  = 1 << 1,
242     };
243 
244     LightOnSphere fLight = {{200 + DX, 200 + DY}, 800, 12};
245 
246     VSphere fSphere;
247     Flags   fFlags;
248 
249 public:
SampleCubeBase(Flags flags)250     SampleCubeBase(Flags flags)
251         : fSphere({200 + DX, 200 + DY}, 400)
252         , fFlags(flags)
253     {}
254 
onChar(SkUnichar uni)255     bool onChar(SkUnichar uni) override {
256         switch (uni) {
257             case 'Z': fLight.fDistance += 10; return true;
258             case 'z': fLight.fDistance -= 10; return true;
259         }
260         return this->Sample3DView::onChar(uni);
261     }
262 
263     virtual void drawContent(SkCanvas* canvas, SkColor, int index, bool drawFront) = 0;
264 
setClickToWorld(SkCanvas * canvas,const SkM44 & clickM)265     void setClickToWorld(SkCanvas* canvas, const SkM44& clickM) {
266         auto l2d = canvas->getLocalToDevice();
267         fWorldToClick = inv(clickM) * l2d;
268         fClickToWorld = inv(fWorldToClick);
269     }
270 
onDrawContent(SkCanvas * canvas)271     void onDrawContent(SkCanvas* canvas) override {
272         if (!canvas->getGrContext() && !(fFlags & kCanRunOnCPU)) {
273             return;
274         }
275         SkM44 clickM = canvas->getLocalToDevice();
276 
277         canvas->save();
278         canvas->translate(DX, DY);
279 
280         this->saveCamera(canvas, {0, 0, 400, 400}, 200);
281 
282         this->setClickToWorld(canvas, clickM);
283 
284         for (bool drawFront : {false, true}) {
285             int index = 0;
286             for (auto f : faces) {
287                 SkAutoCanvasRestore acr(canvas, true);
288 
289                 SkM44 trans = SkM44::Translate(200, 200, 0);   // center of the rotation
290                 SkM44 m = fRotateAnimator.rotation() * fRotation * f.asM44(200);
291 
292                 canvas->concat44(trans * m * inv(trans));
293                 this->drawContent(canvas, f.fColor, index++, drawFront);
294             }
295         }
296 
297         canvas->restore();  // camera
298         canvas->restore();  // center the content in the window
299 
300         if (fFlags & kShowLightDome){
301             fLight.draw(canvas);
302 
303             SkPaint paint;
304             paint.setAntiAlias(true);
305             paint.setStyle(SkPaint::kStroke_Style);
306             paint.setColor(0x40FF0000);
307             canvas->drawCircle(fSphere.fCenter.x, fSphere.fCenter.y, fSphere.fRadius, paint);
308             canvas->drawLine(fSphere.fCenter.x, fSphere.fCenter.y - fSphere.fRadius,
309                              fSphere.fCenter.x, fSphere.fCenter.y + fSphere.fRadius, paint);
310             canvas->drawLine(fSphere.fCenter.x - fSphere.fRadius, fSphere.fCenter.y,
311                              fSphere.fCenter.x + fSphere.fRadius, fSphere.fCenter.y, paint);
312         }
313     }
314 
onFindClickHandler(SkScalar x,SkScalar y,skui::ModifierKey modi)315     Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
316         SkV2 p = fLight.fLoc - SkV2{x, y};
317         if (p.length() <= fLight.fRadius) {
318             Click* c = new Click();
319             c->fMeta.setS32("type", 0);
320             return c;
321         }
322         if (fSphere.contains({x, y})) {
323             Click* c = new Click();
324             c->fMeta.setS32("type", 1);
325 
326             fRotation = fRotateAnimator.rotation() * fRotation;
327             fRotateAnimator.reset();
328             return c;
329         }
330         return nullptr;
331     }
onClick(Click * click)332     bool onClick(Click* click) override {
333 #if 0
334         auto L = fWorldToClick * fLight.fPos;
335         SkPoint c = project(fClickToWorld, {click->fCurr.fX, click->fCurr.fY, L.z/L.w, 1});
336         fLight.update(c.fX, c.fY);
337 #endif
338         if (click->fMeta.hasS32("type", 0)) {
339             fLight.fLoc = fSphere.pinLoc({click->fCurr.fX, click->fCurr.fY});
340             return true;
341         }
342         if (click->fMeta.hasS32("type", 1)) {
343             if (click->fState == skui::InputState::kUp) {
344                 fRotation = fRotateAnimator.rotation() * fRotation;
345                 fRotateAnimator.start();
346             } else {
347                 auto [axis, angle] = fSphere.computeRotationInfo(
348                                                 {click->fOrig.fX, click->fOrig.fY},
349                                                 {click->fCurr.fX, click->fCurr.fY});
350                 fRotateAnimator.update(axis, angle);
351             }
352             return true;
353         }
354         return true;
355     }
356 
onAnimate(double nanos)357     bool onAnimate(double nanos) override {
358         return fRotateAnimator.isAnimating();
359     }
360 
361 private:
362     typedef Sample3DView INHERITED;
363 };
364 
365 class SampleBump3D : public SampleCubeBase {
366     sk_sp<SkShader>        fBmpShader, fImgShader;
367     sk_sp<SkRuntimeEffect> fEffect;
368     SkRRect                fRR;
369 
370 public:
SampleBump3D()371     SampleBump3D() : SampleCubeBase(kShowLightDome) {}
372 
name()373     SkString name() override { return SkString("bump3d"); }
374 
onOnceBeforeDraw()375     void onOnceBeforeDraw() override {
376         fRR = SkRRect::MakeRectXY({20, 20, 380, 380}, 50, 50);
377         auto img = GetResourceAsImage("images/brickwork-texture.jpg");
378         fImgShader = img->makeShader(SkMatrix::MakeScale(2, 2));
379         img = GetResourceAsImage("images/brickwork_normal-map.jpg");
380         fBmpShader = img->makeShader(SkMatrix::MakeScale(2, 2));
381 
382         const char code[] = R"(
383             in fragmentProcessor color_map;
384             in fragmentProcessor normal_map;
385 
386             uniform float4x4 localToWorld;
387             uniform float4x4 localToWorldAdjInv;
388             uniform float3   lightPos;
389 
390             float3 convert_normal_sample(half4 c) {
391                 float3 n = 2 * c.rgb - 1;
392                 n.y = -n.y;
393                 return n;
394             }
395 
396             void main(float2 p, inout half4 color) {
397                 float3 norm = convert_normal_sample(sample(normal_map, p));
398                 float3 plane_norm = normalize(localToWorld * float4(norm, 0)).xyz;
399 
400                 float3 plane_pos = (localToWorld * float4(p, 0, 1)).xyz;
401                 float3 light_dir = normalize(lightPos - plane_pos);
402 
403                 float ambient = 0.2;
404                 float dp = dot(plane_norm, light_dir);
405                 float scale = min(ambient + max(dp, 0), 1);
406 
407                 color = sample(color_map, p) * half4(float4(scale, scale, scale, 1));
408             }
409         )";
410         auto [effect, error] = SkRuntimeEffect::Make(SkString(code));
411         if (!effect) {
412             SkDebugf("runtime error %s\n", error.c_str());
413         }
414         fEffect = effect;
415     }
416 
drawContent(SkCanvas * canvas,SkColor color,int index,bool drawFront)417     void drawContent(SkCanvas* canvas, SkColor color, int index, bool drawFront) override {
418         if (!drawFront || !front(canvas->getLocalToDevice())) {
419             return;
420         }
421 
422         auto adj_inv = [](const SkM44& m) {
423             SkM44 inv;
424             SkAssertResult(m.invert(&inv));
425             return inv.transpose();
426         };
427 
428         struct Uniforms {
429             SkM44  fLocalToWorld;
430             SkM44  fLocalToWorldAdjInv;
431             SkV3   fLightPos;
432         } uni;
433         uni.fLocalToWorld = canvas->experimental_getLocalToWorld();
434         uni.fLocalToWorldAdjInv = adj_inv(uni.fLocalToWorld);
435         uni.fLightPos = fLight.computeWorldPos(fSphere);
436 
437         sk_sp<SkData> data = SkData::MakeWithCopy(&uni, sizeof(uni));
438         sk_sp<SkShader> children[] = { fImgShader, fBmpShader };
439 
440         SkPaint paint;
441         paint.setColor(color);
442         paint.setShader(fEffect->makeShader(data, children, 2, nullptr, true));
443 
444         canvas->drawRRect(fRR, paint);
445     }
446 };
447 DEF_SAMPLE( return new SampleBump3D; )
448 
449 #include "modules/skottie/include/Skottie.h"
450 
451 class SampleSkottieCube : public SampleCubeBase {
452     sk_sp<skottie::Animation> fAnim[6];
453 
454 public:
SampleSkottieCube()455     SampleSkottieCube() : SampleCubeBase(kCanRunOnCPU) {}
456 
name()457     SkString name() override { return SkString("skottie3d"); }
458 
onOnceBeforeDraw()459     void onOnceBeforeDraw() override {
460         const char* files[] = {
461             "skottie/skottie-chained-mattes.json",
462             "skottie/skottie-gradient-ramp.json",
463             "skottie/skottie_sample_2.json",
464             "skottie/skottie-3d-3planes.json",
465             "skottie/skottie-text-animator-4.json",
466             "skottie/skottie-motiontile-effect-phase.json",
467 
468         };
469         for (unsigned i = 0; i < SK_ARRAY_COUNT(files); ++i) {
470             if (auto stream = GetResourceAsStream(files[i])) {
471                 fAnim[i] = skottie::Animation::Make(stream.get());
472             }
473         }
474     }
475 
drawContent(SkCanvas * canvas,SkColor color,int index,bool drawFront)476     void drawContent(SkCanvas* canvas, SkColor color, int index, bool drawFront) override {
477         if (!drawFront || !front(canvas->getLocalToDevice())) {
478             return;
479         }
480 
481         SkPaint paint;
482         paint.setColor(color);
483         SkRect r = {0, 0, 400, 400};
484         canvas->drawRect(r, paint);
485         fAnim[index]->render(canvas, &r);
486     }
487 
onAnimate(double nanos)488     bool onAnimate(double nanos) override {
489         for (auto& anim : fAnim) {
490             SkScalar dur = anim->duration();
491             SkScalar t = fmod(1e-9 * nanos, dur) / dur;
492             anim->seek(t);
493         }
494         return true;
495     }
496 };
497 DEF_SAMPLE( return new SampleSkottieCube; )
498