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