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