blob: d43e6d763a596df75c2542c78a5c9d4937d7c278 [file] [log] [blame]
Robert Phillipsa8cdbd72018-07-17 12:30:40 -04001/*
2 * Copyright 2016 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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "include/core/SkCanvas.h"
9#include "include/core/SkDrawable.h"
10#include "include/core/SkRSXform.h"
11#include "include/utils/SkRandom.h"
12#include "samplecode/Sample.h"
13#include "src/core/SkNormalSource.h"
14#include "src/shaders/SkBitmapProcShader.h"
15#include "src/shaders/SkLightingShader.h"
16#include "src/shaders/SkLights.h"
Robert Phillipsa8cdbd72018-07-17 12:30:40 -040017
Mike Kleinc0bd9f92019-04-23 12:05:21 -050018#include "tools/ToolUtils.h"
Robert Phillipsa8cdbd72018-07-17 12:30:40 -040019
20// A crude normal mapped asteroids-like sample
21class DrawLitAtlasDrawable : public SkDrawable {
22public:
23 DrawLitAtlasDrawable(const SkRect& r)
24 : fBounds(r)
25 , fUseColors(false)
26 , fLightDir(SkVector3::Make(1.0f, 0.0f, 0.0f)) {
27 fAtlas = MakeAtlas();
28
29 SkRandom rand;
30 for (int i = 0; i < kNumAsteroids; ++i) {
31 fAsteroids[i].initAsteroid(&rand, fBounds, &fDiffTex[i], &fNormTex[i]);
32 }
33
34 fShip.initShip(fBounds, &fDiffTex[kNumAsteroids], &fNormTex[kNumAsteroids]);
35
36 this->updateLights();
37 }
38
39 void toggleUseColors() {
40 fUseColors = !fUseColors;
41 }
42
43 void rotateLight() {
Brian Osman4428f2c2019-04-02 10:59:28 -040044 SkScalar r = SK_ScalarPI / 6.0f,
45 s = SkScalarSin(r),
46 c = SkScalarCos(r);
Robert Phillipsa8cdbd72018-07-17 12:30:40 -040047
48 SkScalar newX = c * fLightDir.fX - s * fLightDir.fY;
49 SkScalar newY = s * fLightDir.fX + c * fLightDir.fY;
50
51 fLightDir.set(newX, newY, 0.0f);
52
53 this->updateLights();
54 }
55
56 void left() {
57 SkScalar newRot = SkScalarMod(fShip.rot() + (2*SK_ScalarPI - SK_ScalarPI/32.0f),
58 2 * SK_ScalarPI);
59 fShip.setRot(newRot);
60 }
61
62 void right() {
63 SkScalar newRot = SkScalarMod(fShip.rot() + SK_ScalarPI/32.0f, 2 * SK_ScalarPI);
64 fShip.setRot(newRot);
65 }
66
67 void thrust() {
Brian Osman4428f2c2019-04-02 10:59:28 -040068 SkScalar s = SkScalarSin(fShip.rot()),
69 c = SkScalarCos(fShip.rot());
Robert Phillipsa8cdbd72018-07-17 12:30:40 -040070
71 SkVector newVel = fShip.velocity();
72 newVel.fX += s;
73 newVel.fY += -c;
74
75 SkScalar len = newVel.length();
76 if (len > kMaxShipSpeed) {
77 newVel.setLength(SkIntToScalar(kMaxShipSpeed));
78 }
79
80 fShip.setVelocity(newVel);
81 }
82
83protected:
84 void onDraw(SkCanvas* canvas) override {
85 SkRSXform xforms[kNumAsteroids+kNumShips];
86 SkColor colors[kNumAsteroids+kNumShips];
87
88 for (int i = 0; i < kNumAsteroids; ++i) {
89 fAsteroids[i].advance(fBounds);
90 xforms[i] = fAsteroids[i].asRSXform();
91 if (fUseColors) {
92 colors[i] = SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF);
93 }
94 }
95
96 fShip.advance(fBounds);
97 xforms[kNumAsteroids] = fShip.asRSXform();
98 if (fUseColors) {
99 colors[kNumAsteroids] = SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF);
100 }
101
102#ifdef SK_DEBUG
103 canvas->drawBitmap(fAtlas, 0, 0); // just to see the atlas
104
105 this->drawLightDir(canvas, fBounds.centerX(), fBounds.centerY());
106#endif
107
108#if 0
109 // TODO: revitalize when drawLitAtlas API lands
110 SkPaint paint;
111 paint.setFilterQuality(kLow_SkFilterQuality);
112
113 const SkRect cull = this->getBounds();
114 const SkColor* colorsPtr = fUseColors ? colors : NULL;
115
116 canvas->drawLitAtlas(fAtlas, xforms, fDiffTex, fNormTex, colorsPtr, kNumAsteroids+1,
117 SkXfermode::kModulate_Mode, &cull, &paint, fLights);
118#else
119 SkMatrix diffMat, normalMat;
120
121 for (int i = 0; i < kNumAsteroids+1; ++i) {
122 colors[i] = colors[i] & 0xFF000000; // to silence compilers
123 SkPaint paint;
124
125 SkRect r = fDiffTex[i];
126 r.offsetTo(0, 0);
127
128 diffMat.setRectToRect(fDiffTex[i], r, SkMatrix::kFill_ScaleToFit);
129 normalMat.setRectToRect(fNormTex[i], r, SkMatrix::kFill_ScaleToFit);
130
131 SkMatrix m;
132 m.setRSXform(xforms[i]);
133
Mike Reed50acf8f2019-04-08 13:20:23 -0400134 sk_sp<SkShader> normalMap = fAtlas.makeShader(&normalMat);
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400135 sk_sp<SkNormalSource> normalSource = SkNormalSource::MakeFromNormalMap(
136 std::move(normalMap), m);
Mike Reed50acf8f2019-04-08 13:20:23 -0400137 sk_sp<SkShader> diffuseShader = fAtlas.makeShader(&diffMat);
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400138 paint.setShader(SkLightingShader::Make(std::move(diffuseShader),
139 std::move(normalSource), fLights));
140
141 canvas->save();
142 canvas->setMatrix(m);
143 canvas->drawRect(r, paint);
144 canvas->restore();
145 }
146#endif
147
148#ifdef SK_DEBUG
149 {
150 SkPaint paint;
151 paint.setColor(SK_ColorRED);
152
153 for (int i = 0; i < kNumAsteroids; ++i) {
154 canvas->drawCircle(fAsteroids[i].pos().x(), fAsteroids[i].pos().y(), 2, paint);
155 }
156 canvas->drawCircle(fShip.pos().x(), fShip.pos().y(), 2, paint);
157
158 paint.setStyle(SkPaint::kStroke_Style);
159 canvas->drawRect(this->getBounds(), paint);
160 }
161#endif
162 }
163
164 SkRect onGetBounds() override {
165 return fBounds;
166 }
167
168private:
169
170 enum ObjType {
171 kBigAsteroid_ObjType = 0,
172 kMedAsteroid_ObjType,
173 kSmAsteroid_ObjType,
174 kShip_ObjType,
175
176 kLast_ObjType = kShip_ObjType
177 };
178
179 static const int kObjTypeCount = kLast_ObjType + 1;
180
181 void updateLights() {
182 SkLights::Builder builder;
183
184 builder.add(SkLights::Light::MakeDirectional(
185 SkColor3f::Make(1.0f, 1.0f, 1.0f), fLightDir));
186 builder.setAmbientLightColor(SkColor3f::Make(0.2f, 0.2f, 0.2f));
187
188 fLights = builder.finish();
189 }
190
191#ifdef SK_DEBUG
192 // Draw a vector to the light
193 void drawLightDir(SkCanvas* canvas, SkScalar centerX, SkScalar centerY) {
194 static const int kBgLen = 30;
195 static const int kSmLen = 5;
196
197 // TODO: change the lighting coordinate system to be right handed
198 SkPoint p1 = SkPoint::Make(centerX + kBgLen * fLightDir.fX,
199 centerY - kBgLen * fLightDir.fY);
200 SkPoint p2 = SkPoint::Make(centerX + (kBgLen-kSmLen) * fLightDir.fX,
201 centerY - (kBgLen-kSmLen) * fLightDir.fY);
202
203 SkPaint p;
204 canvas->drawLine(centerX, centerY, p1.fX, p1.fY, p);
205 canvas->drawLine(p1.fX, p1.fY,
206 p2.fX - kSmLen * fLightDir.fY, p2.fY - kSmLen * fLightDir.fX, p);
207 canvas->drawLine(p1.fX, p1.fY,
208 p2.fX + kSmLen * fLightDir.fY, p2.fY + kSmLen * fLightDir.fX, p);
209 }
210#endif
211
212 // Create the mixed diffuse & normal atlas
213 //
214 // big color circle | big normal hemi
215 // ------------------------------------
216 // med color circle | med normal pyra
217 // ------------------------------------
218 // sm color circle | sm normal hemi
219 // ------------------------------------
220 // big ship | big tetra normal
221 static SkBitmap MakeAtlas() {
222
223 SkBitmap atlas;
224 atlas.allocN32Pixels(kAtlasWidth, kAtlasHeight);
225
226 for (int y = 0; y < kAtlasHeight; ++y) {
227 int x = 0;
228 for ( ; x < kBigSize+kPad; ++x) {
229 *atlas.getAddr32(x, y) = SK_ColorTRANSPARENT;
230 }
231 for ( ; x < kAtlasWidth; ++x) {
232 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0x88, 0x88, 0xFF);
233 }
234 }
235
236 // big asteroid
237 {
238 SkPoint bigCenter = SkPoint::Make(kDiffXOff + kBigSize/2.0f, kBigYOff + kBigSize/2.0f);
239
240 for (int y = kBigYOff; y < kBigYOff+kBigSize; ++y) {
241 for (int x = kDiffXOff; x < kDiffXOff+kBigSize; ++x) {
242 SkScalar distSq = (x - bigCenter.fX) * (x - bigCenter.fX) +
243 (y - bigCenter.fY) * (y - bigCenter.fY);
244 if (distSq > kBigSize*kBigSize/4.0f) {
245 *atlas.getAddr32(x, y) = SkPreMultiplyARGB(0, 0, 0, 0);
246 } else {
247 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0xFF, 0, 0);
248 }
249 }
250 }
251
Mike Kleinea3f0142019-03-20 11:12:10 -0500252 ToolUtils::create_hemi_normal_map(
253 &atlas, SkIRect::MakeXYWH(kNormXOff, kBigYOff, kBigSize, kBigSize));
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400254 }
255
256 // medium asteroid
257 {
258 for (int y = kMedYOff; y < kMedYOff+kMedSize; ++y) {
259 for (int x = kDiffXOff; x < kDiffXOff+kMedSize; ++x) {
260 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0xFF, 0);
261 }
262 }
263
Mike Kleinea3f0142019-03-20 11:12:10 -0500264 ToolUtils::create_frustum_normal_map(
265 &atlas, SkIRect::MakeXYWH(kNormXOff, kMedYOff, kMedSize, kMedSize));
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400266 }
267
268 // small asteroid
269 {
270 SkPoint smCenter = SkPoint::Make(kDiffXOff + kSmSize/2.0f, kSmYOff + kSmSize/2.0f);
271
272 for (int y = kSmYOff; y < kSmYOff+kSmSize; ++y) {
273 for (int x = kDiffXOff; x < kDiffXOff+kSmSize; ++x) {
274 SkScalar distSq = (x - smCenter.fX) * (x - smCenter.fX) +
275 (y - smCenter.fY) * (y - smCenter.fY);
276 if (distSq > kSmSize*kSmSize/4.0f) {
277 *atlas.getAddr32(x, y) = SkPreMultiplyARGB(0, 0, 0, 0);
278 } else {
279 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0, 0xFF);
280 }
281 }
282 }
283
Mike Kleinea3f0142019-03-20 11:12:10 -0500284 ToolUtils::create_hemi_normal_map(
285 &atlas, SkIRect::MakeXYWH(kNormXOff, kSmYOff, kSmSize, kSmSize));
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400286 }
287
288 // ship
289 {
290 SkScalar shipMidLine = kDiffXOff + kMedSize/2.0f;
291
292 for (int y = kShipYOff; y < kShipYOff+kMedSize; ++y) {
293 SkScalar scaledY = (y - kShipYOff)/(float)kMedSize; // 0..1
294
295 for (int x = kDiffXOff; x < kDiffXOff+kMedSize; ++x) {
296 SkScalar scaledX;
297
298 if (x < shipMidLine) {
299 scaledX = 1.0f - (x - kDiffXOff)/(kMedSize/2.0f); // 0..1
300 } else {
301 scaledX = (x - shipMidLine)/(kMedSize/2.0f); // 0..1
302 }
303
304 if (scaledX < scaledY) {
305 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0xFF, 0xFF);
306 } else {
307 *atlas.getAddr32(x, y) = SkPackARGB32(0, 0, 0, 0);
308 }
309 }
310 }
311
Mike Kleinea3f0142019-03-20 11:12:10 -0500312 ToolUtils::create_tetra_normal_map(
313 &atlas, SkIRect::MakeXYWH(kNormXOff, kShipYOff, kMedSize, kMedSize));
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400314 }
315
316 return atlas;
317 }
318
319 class ObjectRecord {
320 public:
321 void initAsteroid(SkRandom *rand, const SkRect& bounds,
322 SkRect* diffTex, SkRect* normTex) {
323 static const SkScalar gMaxSpeeds[3] = { 1, 2, 5 }; // smaller asteroids can go faster
324 static const SkScalar gYOffs[3] = { kBigYOff, kMedYOff, kSmYOff };
325 static const SkScalar gSizes[3] = { kBigSize, kMedSize, kSmSize };
326
327 static unsigned int asteroidType = 0;
328 fObjType = static_cast<ObjType>(asteroidType++ % 3);
329
330 fPosition.set(bounds.fLeft + rand->nextUScalar1() * bounds.width(),
331 bounds.fTop + rand->nextUScalar1() * bounds.height());
332 fVelocity.fX = rand->nextSScalar1();
333 fVelocity.fY = sqrt(1.0f - fVelocity.fX * fVelocity.fX);
334 SkASSERT(SkScalarNearlyEqual(fVelocity.length(), 1.0f));
335 fVelocity *= gMaxSpeeds[fObjType];
336 fRot = 0;
337 fDeltaRot = rand->nextSScalar1() / 32;
338
339 diffTex->setXYWH(SkIntToScalar(kDiffXOff), gYOffs[fObjType],
340 gSizes[fObjType], gSizes[fObjType]);
341 normTex->setXYWH(SkIntToScalar(kNormXOff), gYOffs[fObjType],
342 gSizes[fObjType], gSizes[fObjType]);
343 }
344
345 void initShip(const SkRect& bounds, SkRect* diffTex, SkRect* normTex) {
346 fObjType = kShip_ObjType;
347 fPosition.set(bounds.centerX(), bounds.centerY());
348 fVelocity = SkVector::Make(0.0f, 0.0f);
349 fRot = 0.0f;
350 fDeltaRot = 0.0f;
351
352 diffTex->setXYWH(SkIntToScalar(kDiffXOff), SkIntToScalar(kShipYOff),
353 SkIntToScalar(kMedSize), SkIntToScalar(kMedSize));
354 normTex->setXYWH(SkIntToScalar(kNormXOff), SkIntToScalar(kShipYOff),
355 SkIntToScalar(kMedSize), SkIntToScalar(kMedSize));
356 }
357
358 void advance(const SkRect& bounds) {
359 fPosition += fVelocity;
360 if (fPosition.fX > bounds.right()) {
361 SkASSERT(fVelocity.fX > 0);
362 fVelocity.fX = -fVelocity.fX;
363 } else if (fPosition.fX < bounds.left()) {
364 SkASSERT(fVelocity.fX < 0);
365 fVelocity.fX = -fVelocity.fX;
366 }
367 if (fPosition.fY > bounds.bottom()) {
368 if (fVelocity.fY > 0) {
369 fVelocity.fY = -fVelocity.fY;
370 }
371 } else if (fPosition.fY < bounds.top()) {
372 if (fVelocity.fY < 0) {
373 fVelocity.fY = -fVelocity.fY;
374 }
375 }
376
377 fRot += fDeltaRot;
378 fRot = SkScalarMod(fRot, 2 * SK_ScalarPI);
379 }
380
381 const SkPoint& pos() const { return fPosition; }
382
383 SkScalar rot() const { return fRot; }
384 void setRot(SkScalar rot) { fRot = rot; }
385
386 const SkPoint& velocity() const { return fVelocity; }
387 void setVelocity(const SkPoint& velocity) { fVelocity = velocity; }
388
389 SkRSXform asRSXform() const {
390 static const SkScalar gHalfSizes[kObjTypeCount] = {
391 SkScalarHalf(kBigSize),
392 SkScalarHalf(kMedSize),
393 SkScalarHalf(kSmSize),
394 SkScalarHalf(kMedSize),
395 };
396
397 return SkRSXform::MakeFromRadians(1.0f, fRot, fPosition.x(), fPosition.y(),
398 gHalfSizes[fObjType],
399 gHalfSizes[fObjType]);
400 }
401
402 private:
403 ObjType fObjType;
404 SkPoint fPosition;
405 SkVector fVelocity;
406 SkScalar fRot; // In radians.
407 SkScalar fDeltaRot; // In radiands. Not used by ship.
408 };
409
410private:
411 static const int kNumLights = 2;
412 static const int kNumAsteroids = 6;
413 static const int kNumShips = 1;
414
415 static const int kBigSize = 128;
416 static const int kMedSize = 64;
417 static const int kSmSize = 32;
418 static const int kPad = 1;
419 static const int kAtlasWidth = kBigSize + kBigSize + 2 * kPad; // 2 pads in the middle
420 static const int kAtlasHeight = kBigSize + kMedSize + kSmSize + kMedSize + 3 * kPad;
421
422 static const int kDiffXOff = 0;
423 static const int kNormXOff = kBigSize + 2 * kPad;
424
425 static const int kBigYOff = 0;
426 static const int kMedYOff = kBigSize + kPad;
427 static const int kSmYOff = kMedYOff + kMedSize + kPad;
428 static const int kShipYOff = kSmYOff + kSmSize + kPad;
429 static const int kMaxShipSpeed = 5;
430
431 SkBitmap fAtlas;
432 ObjectRecord fAsteroids[kNumAsteroids];
433 ObjectRecord fShip;
434 SkRect fDiffTex[kNumAsteroids+kNumShips];
435 SkRect fNormTex[kNumAsteroids+kNumShips];
436 SkRect fBounds;
437 bool fUseColors;
438 SkVector3 fLightDir;
439 sk_sp<SkLights> fLights;
440
441 typedef SkDrawable INHERITED;
442};
443
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400444class DrawLitAtlasView : public Sample {
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400445public:
446 DrawLitAtlasView() : fDrawable(new DrawLitAtlasDrawable(SkRect::MakeWH(640, 480))) {}
447
448protected:
Hal Canary8a027312019-07-03 10:55:44 -0400449 SkString name() override { return SkString("DrawLitAtlas"); }
450
Hal Canary6cc65e12019-07-03 15:53:04 -0400451 bool onChar(SkUnichar uni) override {
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400452 switch (uni) {
453 case 'C':
454 fDrawable->toggleUseColors();
455 return true;
456 case 'j':
457 fDrawable->left();
458 return true;
459 case 'k':
460 fDrawable->thrust();
461 return true;
462 case 'l':
463 fDrawable->right();
464 return true;
465 case 'o':
466 fDrawable->rotateLight();
467 return true;
468 default:
469 break;
470 }
Hal Canary6cc65e12019-07-03 15:53:04 -0400471 return false;
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400472 }
473
474 void onDrawContent(SkCanvas* canvas) override {
475 canvas->drawDrawable(fDrawable.get());
476 }
477
Hal Canary41248072019-07-11 16:32:53 -0400478 bool onAnimate(double nanos) override { return true; }
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400479
480private:
481 sk_sp<DrawLitAtlasDrawable> fDrawable;
482
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400483 typedef Sample INHERITED;
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400484};
485
486//////////////////////////////////////////////////////////////////////////////
487
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400488DEF_SAMPLE( return new DrawLitAtlasView(); )