blob: 0caa247a35a409fb3fa070c5988e5c7cbe45f8a5 [file] [log] [blame]
Mike Reed69ace2a2020-01-11 15:57:14 -05001/*
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"
Mike Reed69ace2a2020-01-11 15:57:14 -05009#include "include/core/SkPaint.h"
10#include "include/core/SkRRect.h"
Mike Reed75435872020-01-13 21:15:06 -050011#include "include/private/SkM44.h"
Mike Reed69ace2a2020-01-11 15:57:14 -050012#include "include/utils/SkRandom.h"
13#include "samplecode/Sample.h"
14#include "tools/Resources.h"
15
Mike Reede5809952020-01-25 20:42:51 -050016struct VSphere {
Mike Reed2d4a28e2020-01-25 22:39:43 -050017 SkVec2 fCenter;
Mike Reede5809952020-01-25 20:42:51 -050018 SkScalar fRadius;
19
20 VSphere(SkVec2 center, SkScalar radius) : fCenter(center), fRadius(radius) {}
21
22 bool contains(SkVec2 v) const {
23 return (v - fCenter).length() <= fRadius;
24 }
25
Mike Reed2d4a28e2020-01-25 22:39:43 -050026 SkVec2 pinLoc(SkVec2 p) const {
27 auto v = p - fCenter;
28 if (v.length() > fRadius) {
29 v *= (fRadius / v.length());
30 }
31 return fCenter + v;
32 }
33
34 SkV3 computeUnitV3(SkVec2 v) const {
Mike Reede5809952020-01-25 20:42:51 -050035 v = (v - fCenter) * (1 / fRadius);
36 SkScalar len2 = v.lengthSquared();
37 if (len2 > 1) {
Mike Reede1a81ba2020-02-19 17:40:32 -050038 v = v.normalize();
Mike Reede5809952020-01-25 20:42:51 -050039 len2 = 1;
40 }
41 SkScalar z = SkScalarSqrt(1 - len2);
42 return {v.x, v.y, z};
43 }
44
Mike Reed52638372020-02-19 13:30:06 -050045 struct RotateInfo {
46 SkV3 fAxis;
47 SkScalar fAngle;
48 };
49
50 RotateInfo computeRotationInfo(SkVec2 a, SkVec2 b) const {
Mike Reede5809952020-01-25 20:42:51 -050051 SkV3 u = this->computeUnitV3(a);
52 SkV3 v = this->computeUnitV3(b);
53 SkV3 axis = u.cross(v);
Mike Reed23823662020-02-19 17:07:04 -050054 SkScalar length = axis.length();
Mike Reede5809952020-01-25 20:42:51 -050055
Mike Reed23823662020-02-19 17:07:04 -050056 if (!SkScalarNearlyZero(length)) {
57 return {axis * (1.0f / length), acos(u.dot(v))};
Mike Reede5809952020-01-25 20:42:51 -050058 }
Mike Reed52638372020-02-19 13:30:06 -050059 return {{0, 0, 0}, 0};
60 }
61
62 SkM44 computeRotation(SkVec2 a, SkVec2 b) const {
63 auto [axis, angle] = this->computeRotationInfo(a, b);
64 return SkM44::Rotate(axis, angle);
Mike Reede5809952020-01-25 20:42:51 -050065 }
66};
67
Mike Reedee3216d2020-01-17 17:35:04 -050068static SkM44 inv(const SkM44& m) {
69 SkM44 inverse;
70 SkAssertResult(m.invert(&inverse));
71 return inverse;
72}
73
Mike Reed69ace2a2020-01-11 15:57:14 -050074class Sample3DView : public Sample {
75protected:
76 float fNear = 0.05f;
77 float fFar = 4;
78 float fAngle = SK_ScalarPI / 12;
79
Mike Reed00a97642020-01-25 18:42:23 -050080 SkV3 fEye { 0, 0, 1.0f/tan(fAngle/2) - 1 };
81 SkV3 fCOA { 0, 0, 0 };
82 SkV3 fUp { 0, 1, 0 };
Mike Reed69ace2a2020-01-11 15:57:14 -050083
Mike Reed69ace2a2020-01-11 15:57:14 -050084public:
Mike Reedee0a03a2020-01-14 16:44:47 -050085 void saveCamera(SkCanvas* canvas, const SkRect& area, SkScalar zscale) {
Mike Reed00a97642020-01-25 18:42:23 -050086 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);
Mike Reed69ace2a2020-01-11 15:57:14 -050090
Mike Reedee3216d2020-01-17 17:35:04 -050091 // want "world" to be in our big coordinates (e.g. area), so apply this inverse
92 // as part of our "camera".
Mike Reedc43f2a02020-01-16 14:54:34 -050093 canvas->experimental_saveCamera(viewport * perspective, camera * inv(viewport));
Mike Reed69ace2a2020-01-11 15:57:14 -050094 }
Mike Reed69ace2a2020-01-11 15:57:14 -050095};
96
Mike Reed69ace2a2020-01-11 15:57:14 -050097struct Face {
98 SkScalar fRx, fRy;
Mike Reedee3216d2020-01-17 17:35:04 -050099 SkColor fColor;
Mike Reed69ace2a2020-01-11 15:57:14 -0500100
Mike Reed00a97642020-01-25 18:42:23 -0500101 static SkM44 T(SkScalar x, SkScalar y, SkScalar z) {
102 return SkM44::Translate(x, y, z);
Mike Reed78184a32020-01-25 03:20:18 +0000103 }
104
Mike Reed00a97642020-01-25 18:42:23 -0500105 static SkM44 R(SkV3 axis, SkScalar rad) {
106 return SkM44::Rotate(axis, rad);
Mike Reed78184a32020-01-25 03:20:18 +0000107 }
108
Mike Reed00a97642020-01-25 18:42:23 -0500109 SkM44 asM44(SkScalar scale) const {
110 return R({0,1,0}, fRy) * R({1,0,0}, fRx) * T(0, 0, scale);
Mike Reed69ace2a2020-01-11 15:57:14 -0500111 }
112};
113
Mike Reed75435872020-01-13 21:15:06 -0500114static bool front(const SkM44& m) {
Florin Malitad5899162020-02-04 10:06:24 -0500115 SkM44 m2(SkM44::kUninitialized_Constructor);
116 if (!m.invert(&m2)) {
117 m2.setIdentity();
118 }
Mike Reed75435872020-01-13 21:15:06 -0500119 /*
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 */
Mike Reed07d32b42020-01-23 11:06:20 -0500125 return m2.rc(2,2) > 0;
Mike Reed69ace2a2020-01-11 15:57:14 -0500126}
127
128const Face faces[] = {
Mike Reedee3216d2020-01-17 17:35:04 -0500129 { 0, 0, SK_ColorRED }, // front
130 { 0, SK_ScalarPI, SK_ColorGREEN }, // back
Mike Reed69ace2a2020-01-11 15:57:14 -0500131
Mike Reedee3216d2020-01-17 17:35:04 -0500132 { SK_ScalarPI/2, 0, SK_ColorBLUE }, // top
133 {-SK_ScalarPI/2, 0, SK_ColorCYAN }, // bottom
Mike Reed69ace2a2020-01-11 15:57:14 -0500134
Mike Reedee3216d2020-01-17 17:35:04 -0500135 { 0, SK_ScalarPI/2, SK_ColorMAGENTA }, // left
136 { 0,-SK_ScalarPI/2, SK_ColorYELLOW }, // right
Mike Reed69ace2a2020-01-11 15:57:14 -0500137};
138
Mike Reedee3216d2020-01-17 17:35:04 -0500139#include "include/effects/SkRuntimeEffect.h"
140
Mike Reed2d4a28e2020-01-25 22:39:43 -0500141struct LightOnSphere {
142 SkVec2 fLoc;
143 SkScalar fDistance;
144 SkScalar fRadius;
145
146 SkV3 computeWorldPos(const VSphere& s) const {
147 return s.computeUnitV3(fLoc) * fDistance;
148 }
149
150 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
Mike Reed52638372020-02-19 13:30:06 -0500160#include "include/core/SkTime.h"
161
162class 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
175public:
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
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
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
216 void reset() {
217 fAngleSpeed = 0;
218 fAngle = 0;
219 fPrevAngle = 1234567;
220 }
221};
222
Mike Reedcfee8ee2020-02-18 13:05:45 -0500223class SampleCubeBase : public Sample3DView {
Mike Reed2d4a28e2020-01-25 22:39:43 -0500224 enum {
225 DX = 400,
226 DY = 300
227 };
228
Mike Reedf0b7edf2020-01-18 14:21:12 -0500229 SkM44 fWorldToClick,
230 fClickToWorld;
231
Mike Reed52638372020-02-19 13:30:06 -0500232 SkM44 fRotation; // part of model
233
234 RotateAnimator fRotateAnimator;
Mike Reede5809952020-01-25 20:42:51 -0500235
Mike Reedcfee8ee2020-02-18 13:05:45 -0500236protected:
237 enum Flags {
238 kCanRunOnCPU = 1 << 0,
239 kShowLightDome = 1 << 1,
240 };
241
242 LightOnSphere fLight = {{200 + DX, 200 + DY}, 800, 12};
243
244 VSphere fSphere;
245 Flags fFlags;
246
Mike Reede5809952020-01-25 20:42:51 -0500247public:
Mike Reedcfee8ee2020-02-18 13:05:45 -0500248 SampleCubeBase(Flags flags)
249 : fSphere({200 + DX, 200 + DY}, 400)
250 , fFlags(flags)
251 {}
Mike Reedf0b7edf2020-01-18 14:21:12 -0500252
253 bool onChar(SkUnichar uni) override {
254 switch (uni) {
Mike Reed2d4a28e2020-01-25 22:39:43 -0500255 case 'Z': fLight.fDistance += 10; return true;
256 case 'z': fLight.fDistance -= 10; return true;
Mike Reedf0b7edf2020-01-18 14:21:12 -0500257 }
258 return this->Sample3DView::onChar(uni);
259 }
260
Mike Reedcfee8ee2020-02-18 13:05:45 -0500261 virtual void drawContent(SkCanvas* canvas, SkColor, int index, bool drawFront) = 0;
Mike Reedf0b7edf2020-01-18 14:21:12 -0500262
263 void setClickToWorld(SkCanvas* canvas, const SkM44& clickM) {
264 auto l2d = canvas->experimental_getLocalToDevice();
265 fWorldToClick = inv(clickM) * l2d;
266 fClickToWorld = inv(fWorldToClick);
267 }
268
269 void onDrawContent(SkCanvas* canvas) override {
Mike Reedcfee8ee2020-02-18 13:05:45 -0500270 if (!canvas->getGrContext() && !(fFlags & kCanRunOnCPU)) {
Mike Reedf0b7edf2020-01-18 14:21:12 -0500271 return;
272 }
273 SkM44 clickM = canvas->experimental_getLocalToDevice();
274
275 canvas->save();
Mike Reed2d4a28e2020-01-25 22:39:43 -0500276 canvas->translate(DX, DY);
Mike Reedf0b7edf2020-01-18 14:21:12 -0500277
278 this->saveCamera(canvas, {0, 0, 400, 400}, 200);
279
280 this->setClickToWorld(canvas, clickM);
281
Mike Reedcfee8ee2020-02-18 13:05:45 -0500282 for (bool drawFront : {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
Mike Reed52638372020-02-19 13:30:06 -0500288 SkM44 m = fRotateAnimator.rotation() * fRotation * f.asM44(200);
Mike Reedcfee8ee2020-02-18 13:05:45 -0500289
Mike Reedbd3de192020-02-20 08:35:33 -0500290 canvas->experimental_concat44(trans * m * inv(trans));
Mike Reedcfee8ee2020-02-18 13:05:45 -0500291 this->drawContent(canvas, f.fColor, index++, drawFront);
292 }
Mike Reedf0b7edf2020-01-18 14:21:12 -0500293 }
294
Mike Reede5809952020-01-25 20:42:51 -0500295 canvas->restore(); // camera
296 canvas->restore(); // center the content in the window
297
Mike Reedcfee8ee2020-02-18 13:05:45 -0500298 if (fFlags & kShowLightDome){
299 fLight.draw(canvas);
300
Mike Reede5809952020-01-25 20:42:51 -0500301 SkPaint paint;
Mike Reed2d4a28e2020-01-25 22:39:43 -0500302 paint.setAntiAlias(true);
Mike Reede5809952020-01-25 20:42:51 -0500303 paint.setStyle(SkPaint::kStroke_Style);
Mike Reed2d4a28e2020-01-25 22:39:43 -0500304 paint.setColor(0x40FF0000);
Mike Reede5809952020-01-25 20:42:51 -0500305 canvas->drawCircle(fSphere.fCenter.x, fSphere.fCenter.y, fSphere.fRadius, paint);
306 canvas->drawLine(fSphere.fCenter.x, fSphere.fCenter.y - fSphere.fRadius,
307 fSphere.fCenter.x, fSphere.fCenter.y + fSphere.fRadius, paint);
308 canvas->drawLine(fSphere.fCenter.x - fSphere.fRadius, fSphere.fCenter.y,
309 fSphere.fCenter.x + fSphere.fRadius, fSphere.fCenter.y, paint);
310 }
Mike Reedf0b7edf2020-01-18 14:21:12 -0500311 }
312
313 Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
Mike Reed2d4a28e2020-01-25 22:39:43 -0500314 SkVec2 p = fLight.fLoc - SkVec2{x, y};
315 if (p.length() <= fLight.fRadius) {
Mike Reede5809952020-01-25 20:42:51 -0500316 Click* c = new Click();
317 c->fMeta.setS32("type", 0);
318 return c;
319 }
320 if (fSphere.contains({x, y})) {
321 Click* c = new Click();
322 c->fMeta.setS32("type", 1);
Mike Reed52638372020-02-19 13:30:06 -0500323
324 fRotation = fRotateAnimator.rotation() * fRotation;
325 fRotateAnimator.reset();
Mike Reede5809952020-01-25 20:42:51 -0500326 return c;
Mike Reedf0b7edf2020-01-18 14:21:12 -0500327 }
328 return nullptr;
329 }
330 bool onClick(Click* click) override {
Mike Reed2d4a28e2020-01-25 22:39:43 -0500331#if 0
332 auto L = fWorldToClick * fLight.fPos;
333 SkPoint c = project(fClickToWorld, {click->fCurr.fX, click->fCurr.fY, L.z/L.w, 1});
334 fLight.update(c.fX, c.fY);
335#endif
Mike Reede5809952020-01-25 20:42:51 -0500336 if (click->fMeta.hasS32("type", 0)) {
Mike Reed2d4a28e2020-01-25 22:39:43 -0500337 fLight.fLoc = fSphere.pinLoc({click->fCurr.fX, click->fCurr.fY});
Mike Reede5809952020-01-25 20:42:51 -0500338 return true;
339 }
340 if (click->fMeta.hasS32("type", 1)) {
341 if (click->fState == skui::InputState::kUp) {
Mike Reed52638372020-02-19 13:30:06 -0500342 fRotation = fRotateAnimator.rotation() * fRotation;
343 fRotateAnimator.start();
Mike Reede5809952020-01-25 20:42:51 -0500344 } else {
Mike Reed52638372020-02-19 13:30:06 -0500345 auto [axis, angle] = fSphere.computeRotationInfo(
346 {click->fOrig.fX, click->fOrig.fY},
347 {click->fCurr.fX, click->fCurr.fY});
348 fRotateAnimator.update(axis, angle);
Mike Reede5809952020-01-25 20:42:51 -0500349 }
350 return true;
351 }
Mike Reedf0b7edf2020-01-18 14:21:12 -0500352 return true;
353 }
Mike Reed52638372020-02-19 13:30:06 -0500354
355 bool onAnimate(double nanos) override {
356 // handle fling
357 return this->INHERITED::onAnimate(nanos);
358 }
359
360private:
361 typedef Sample3DView INHERITED;
Mike Reedf0b7edf2020-01-18 14:21:12 -0500362};
Mike Reedcfee8ee2020-02-18 13:05:45 -0500363
364class SampleBump3D : public SampleCubeBase {
365 sk_sp<SkShader> fBmpShader, fImgShader;
366 sk_sp<SkRuntimeEffect> fEffect;
367 SkRRect fRR;
368
369public:
370 SampleBump3D() : SampleCubeBase(kShowLightDome) {}
371
372 SkString name() override { return SkString("bump3d"); }
373
374 void onOnceBeforeDraw() override {
375 fRR = SkRRect::MakeRectXY({20, 20, 380, 380}, 50, 50);
376 auto img = GetResourceAsImage("images/brickwork-texture.jpg");
377 fImgShader = img->makeShader(SkMatrix::MakeScale(2, 2));
378 img = GetResourceAsImage("images/brickwork_normal-map.jpg");
379 fBmpShader = img->makeShader(SkMatrix::MakeScale(2, 2));
380
381 const char code[] = R"(
382 in fragmentProcessor color_map;
383 in fragmentProcessor normal_map;
384
385 uniform float4x4 localToWorld;
386 uniform float4x4 localToWorldAdjInv;
387 uniform float3 lightPos;
388
389 float3 convert_normal_sample(half4 c) {
390 float3 n = 2 * c.rgb - 1;
391 n.y = -n.y;
392 return n;
393 }
394
395 void main(float2 p, inout half4 color) {
396 float3 norm = convert_normal_sample(sample(normal_map, p));
397 float3 plane_norm = normalize(localToWorld * float4(norm, 0)).xyz;
398
399 float3 plane_pos = (localToWorld * float4(p, 0, 1)).xyz;
400 float3 light_dir = normalize(lightPos - plane_pos);
401
402 float ambient = 0.2;
403 float dp = dot(plane_norm, light_dir);
404 float scale = min(ambient + max(dp, 0), 1);
405
406 color = sample(color_map, p) * half4(float4(scale, scale, scale, 1));
407 }
408 )";
409 auto [effect, error] = SkRuntimeEffect::Make(SkString(code));
410 if (!effect) {
411 SkDebugf("runtime error %s\n", error.c_str());
412 }
413 fEffect = effect;
414 }
415
416 void drawContent(SkCanvas* canvas, SkColor color, int index, bool drawFront) override {
417 if (!drawFront || !front(canvas->experimental_getLocalToDevice())) {
418 return;
419 }
420
421 auto adj_inv = [](const SkM44& m) {
422 SkM44 inv;
423 SkAssertResult(m.invert(&inv));
424 return inv.transpose();
425 };
426
427 struct Uniforms {
428 SkM44 fLocalToWorld;
429 SkM44 fLocalToWorldAdjInv;
430 SkV3 fLightPos;
431 } uni;
432 uni.fLocalToWorld = canvas->experimental_getLocalToWorld();
433 uni.fLocalToWorldAdjInv = adj_inv(uni.fLocalToWorld);
434 uni.fLightPos = fLight.computeWorldPos(fSphere);
435
436 sk_sp<SkData> data = SkData::MakeWithCopy(&uni, sizeof(uni));
437 sk_sp<SkShader> children[] = { fImgShader, fBmpShader };
438
439 SkPaint paint;
440 paint.setColor(color);
441 paint.setShader(fEffect->makeShader(data, children, 2, nullptr, true));
442
443 canvas->drawRRect(fRR, paint);
444 }
445};
Mike Reede5809952020-01-25 20:42:51 -0500446DEF_SAMPLE( return new SampleBump3D; )
Mike Reedcfee8ee2020-02-18 13:05:45 -0500447
448#include "modules/skottie/include/Skottie.h"
449
450class SampleSkottieCube : public SampleCubeBase {
451 sk_sp<skottie::Animation> fAnim[6];
452
453public:
454 SampleSkottieCube() : SampleCubeBase(kCanRunOnCPU) {}
455
456 SkString name() override { return SkString("skottie3d"); }
457
458 void onOnceBeforeDraw() override {
459 const char* files[] = {
460 "skottie/skottie-chained-mattes.json",
461 "skottie/skottie-gradient-ramp.json",
462 "skottie/skottie_sample_2.json",
463 "skottie/skottie-3d-3planes.json",
464 "skottie/skottie-text-animator-4.json",
465 "skottie/skottie-motiontile-effect-phase.json",
466
467 };
468 for (unsigned i = 0; i < SK_ARRAY_COUNT(files); ++i) {
469 if (auto stream = GetResourceAsStream(files[i])) {
470 fAnim[i] = skottie::Animation::Make(stream.get());
471 }
472 }
473 }
474
475 void drawContent(SkCanvas* canvas, SkColor color, int index, bool drawFront) override {
476 if (!drawFront || !front(canvas->experimental_getLocalToDevice())) {
477 return;
478 }
479
480 SkPaint paint;
481 paint.setColor(color);
482 SkRect r = {0, 0, 400, 400};
483 canvas->drawRect(r, paint);
484 fAnim[index]->render(canvas, &r);
485 }
486
487 bool onAnimate(double nanos) override {
488 for (auto& anim : fAnim) {
489 SkScalar dur = anim->duration();
490 SkScalar t = fmod(1e-9 * nanos, dur) / dur;
491 anim->seek(t);
492 }
493 return true;
494 }
495};
496DEF_SAMPLE( return new SampleSkottieCube; )