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