blob: 6a39a53913fe061d45bb322e3954052eb5a4cfaa [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 Kleincd5104e2019-03-20 11:55:08 -05008#include "AnimTimer.h"
Ben Wagnerb2c4ea62018-08-08 11:36:17 -04009#include "Sample.h"
Robert Phillipsa8cdbd72018-07-17 12:30:40 -040010#include "SkBitmapProcShader.h"
11#include "SkCanvas.h"
12#include "SkDrawable.h"
13#include "SkLightingShader.h"
14#include "SkLights.h"
15#include "SkNormalSource.h"
Robert Phillipsa8cdbd72018-07-17 12:30:40 -040016#include "SkRSXform.h"
Mike Kleincd5104e2019-03-20 11:55:08 -050017#include "SkRandom.h"
Robert Phillipsa8cdbd72018-07-17 12:30:40 -040018
Mike Kleinea3f0142019-03-20 11:12:10 -050019#include "ToolUtils.h"
Robert Phillipsa8cdbd72018-07-17 12:30:40 -040020
21// A crude normal mapped asteroids-like sample
22class DrawLitAtlasDrawable : public SkDrawable {
23public:
24 DrawLitAtlasDrawable(const SkRect& r)
25 : fBounds(r)
26 , fUseColors(false)
27 , fLightDir(SkVector3::Make(1.0f, 0.0f, 0.0f)) {
28 fAtlas = MakeAtlas();
29
30 SkRandom rand;
31 for (int i = 0; i < kNumAsteroids; ++i) {
32 fAsteroids[i].initAsteroid(&rand, fBounds, &fDiffTex[i], &fNormTex[i]);
33 }
34
35 fShip.initShip(fBounds, &fDiffTex[kNumAsteroids], &fNormTex[kNumAsteroids]);
36
37 this->updateLights();
38 }
39
40 void toggleUseColors() {
41 fUseColors = !fUseColors;
42 }
43
44 void rotateLight() {
45 SkScalar c;
46 SkScalar s = SkScalarSinCos(SK_ScalarPI/6.0f, &c);
47
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() {
68 SkScalar c;
69 SkScalar s = SkScalarSinCos(fShip.rot(), &c);
70
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
134 sk_sp<SkShader> normalMap = SkShader::MakeBitmapShader(fAtlas, SkShader::kClamp_TileMode,
135 SkShader::kClamp_TileMode, &normalMat);
136 sk_sp<SkNormalSource> normalSource = SkNormalSource::MakeFromNormalMap(
137 std::move(normalMap), m);
138 sk_sp<SkShader> diffuseShader = SkShader::MakeBitmapShader(fAtlas,
139 SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, &diffMat);
140 paint.setShader(SkLightingShader::Make(std::move(diffuseShader),
141 std::move(normalSource), fLights));
142
143 canvas->save();
144 canvas->setMatrix(m);
145 canvas->drawRect(r, paint);
146 canvas->restore();
147 }
148#endif
149
150#ifdef SK_DEBUG
151 {
152 SkPaint paint;
153 paint.setColor(SK_ColorRED);
154
155 for (int i = 0; i < kNumAsteroids; ++i) {
156 canvas->drawCircle(fAsteroids[i].pos().x(), fAsteroids[i].pos().y(), 2, paint);
157 }
158 canvas->drawCircle(fShip.pos().x(), fShip.pos().y(), 2, paint);
159
160 paint.setStyle(SkPaint::kStroke_Style);
161 canvas->drawRect(this->getBounds(), paint);
162 }
163#endif
164 }
165
166 SkRect onGetBounds() override {
167 return fBounds;
168 }
169
170private:
171
172 enum ObjType {
173 kBigAsteroid_ObjType = 0,
174 kMedAsteroid_ObjType,
175 kSmAsteroid_ObjType,
176 kShip_ObjType,
177
178 kLast_ObjType = kShip_ObjType
179 };
180
181 static const int kObjTypeCount = kLast_ObjType + 1;
182
183 void updateLights() {
184 SkLights::Builder builder;
185
186 builder.add(SkLights::Light::MakeDirectional(
187 SkColor3f::Make(1.0f, 1.0f, 1.0f), fLightDir));
188 builder.setAmbientLightColor(SkColor3f::Make(0.2f, 0.2f, 0.2f));
189
190 fLights = builder.finish();
191 }
192
193#ifdef SK_DEBUG
194 // Draw a vector to the light
195 void drawLightDir(SkCanvas* canvas, SkScalar centerX, SkScalar centerY) {
196 static const int kBgLen = 30;
197 static const int kSmLen = 5;
198
199 // TODO: change the lighting coordinate system to be right handed
200 SkPoint p1 = SkPoint::Make(centerX + kBgLen * fLightDir.fX,
201 centerY - kBgLen * fLightDir.fY);
202 SkPoint p2 = SkPoint::Make(centerX + (kBgLen-kSmLen) * fLightDir.fX,
203 centerY - (kBgLen-kSmLen) * fLightDir.fY);
204
205 SkPaint p;
206 canvas->drawLine(centerX, centerY, p1.fX, p1.fY, p);
207 canvas->drawLine(p1.fX, p1.fY,
208 p2.fX - kSmLen * fLightDir.fY, p2.fY - kSmLen * fLightDir.fX, p);
209 canvas->drawLine(p1.fX, p1.fY,
210 p2.fX + kSmLen * fLightDir.fY, p2.fY + kSmLen * fLightDir.fX, p);
211 }
212#endif
213
214 // Create the mixed diffuse & normal atlas
215 //
216 // big color circle | big normal hemi
217 // ------------------------------------
218 // med color circle | med normal pyra
219 // ------------------------------------
220 // sm color circle | sm normal hemi
221 // ------------------------------------
222 // big ship | big tetra normal
223 static SkBitmap MakeAtlas() {
224
225 SkBitmap atlas;
226 atlas.allocN32Pixels(kAtlasWidth, kAtlasHeight);
227
228 for (int y = 0; y < kAtlasHeight; ++y) {
229 int x = 0;
230 for ( ; x < kBigSize+kPad; ++x) {
231 *atlas.getAddr32(x, y) = SK_ColorTRANSPARENT;
232 }
233 for ( ; x < kAtlasWidth; ++x) {
234 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0x88, 0x88, 0xFF);
235 }
236 }
237
238 // big asteroid
239 {
240 SkPoint bigCenter = SkPoint::Make(kDiffXOff + kBigSize/2.0f, kBigYOff + kBigSize/2.0f);
241
242 for (int y = kBigYOff; y < kBigYOff+kBigSize; ++y) {
243 for (int x = kDiffXOff; x < kDiffXOff+kBigSize; ++x) {
244 SkScalar distSq = (x - bigCenter.fX) * (x - bigCenter.fX) +
245 (y - bigCenter.fY) * (y - bigCenter.fY);
246 if (distSq > kBigSize*kBigSize/4.0f) {
247 *atlas.getAddr32(x, y) = SkPreMultiplyARGB(0, 0, 0, 0);
248 } else {
249 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0xFF, 0, 0);
250 }
251 }
252 }
253
Mike Kleinea3f0142019-03-20 11:12:10 -0500254 ToolUtils::create_hemi_normal_map(
255 &atlas, SkIRect::MakeXYWH(kNormXOff, kBigYOff, kBigSize, kBigSize));
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400256 }
257
258 // medium asteroid
259 {
260 for (int y = kMedYOff; y < kMedYOff+kMedSize; ++y) {
261 for (int x = kDiffXOff; x < kDiffXOff+kMedSize; ++x) {
262 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0xFF, 0);
263 }
264 }
265
Mike Kleinea3f0142019-03-20 11:12:10 -0500266 ToolUtils::create_frustum_normal_map(
267 &atlas, SkIRect::MakeXYWH(kNormXOff, kMedYOff, kMedSize, kMedSize));
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400268 }
269
270 // small asteroid
271 {
272 SkPoint smCenter = SkPoint::Make(kDiffXOff + kSmSize/2.0f, kSmYOff + kSmSize/2.0f);
273
274 for (int y = kSmYOff; y < kSmYOff+kSmSize; ++y) {
275 for (int x = kDiffXOff; x < kDiffXOff+kSmSize; ++x) {
276 SkScalar distSq = (x - smCenter.fX) * (x - smCenter.fX) +
277 (y - smCenter.fY) * (y - smCenter.fY);
278 if (distSq > kSmSize*kSmSize/4.0f) {
279 *atlas.getAddr32(x, y) = SkPreMultiplyARGB(0, 0, 0, 0);
280 } else {
281 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0, 0xFF);
282 }
283 }
284 }
285
Mike Kleinea3f0142019-03-20 11:12:10 -0500286 ToolUtils::create_hemi_normal_map(
287 &atlas, SkIRect::MakeXYWH(kNormXOff, kSmYOff, kSmSize, kSmSize));
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400288 }
289
290 // ship
291 {
292 SkScalar shipMidLine = kDiffXOff + kMedSize/2.0f;
293
294 for (int y = kShipYOff; y < kShipYOff+kMedSize; ++y) {
295 SkScalar scaledY = (y - kShipYOff)/(float)kMedSize; // 0..1
296
297 for (int x = kDiffXOff; x < kDiffXOff+kMedSize; ++x) {
298 SkScalar scaledX;
299
300 if (x < shipMidLine) {
301 scaledX = 1.0f - (x - kDiffXOff)/(kMedSize/2.0f); // 0..1
302 } else {
303 scaledX = (x - shipMidLine)/(kMedSize/2.0f); // 0..1
304 }
305
306 if (scaledX < scaledY) {
307 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0xFF, 0xFF);
308 } else {
309 *atlas.getAddr32(x, y) = SkPackARGB32(0, 0, 0, 0);
310 }
311 }
312 }
313
Mike Kleinea3f0142019-03-20 11:12:10 -0500314 ToolUtils::create_tetra_normal_map(
315 &atlas, SkIRect::MakeXYWH(kNormXOff, kShipYOff, kMedSize, kMedSize));
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400316 }
317
318 return atlas;
319 }
320
321 class ObjectRecord {
322 public:
323 void initAsteroid(SkRandom *rand, const SkRect& bounds,
324 SkRect* diffTex, SkRect* normTex) {
325 static const SkScalar gMaxSpeeds[3] = { 1, 2, 5 }; // smaller asteroids can go faster
326 static const SkScalar gYOffs[3] = { kBigYOff, kMedYOff, kSmYOff };
327 static const SkScalar gSizes[3] = { kBigSize, kMedSize, kSmSize };
328
329 static unsigned int asteroidType = 0;
330 fObjType = static_cast<ObjType>(asteroidType++ % 3);
331
332 fPosition.set(bounds.fLeft + rand->nextUScalar1() * bounds.width(),
333 bounds.fTop + rand->nextUScalar1() * bounds.height());
334 fVelocity.fX = rand->nextSScalar1();
335 fVelocity.fY = sqrt(1.0f - fVelocity.fX * fVelocity.fX);
336 SkASSERT(SkScalarNearlyEqual(fVelocity.length(), 1.0f));
337 fVelocity *= gMaxSpeeds[fObjType];
338 fRot = 0;
339 fDeltaRot = rand->nextSScalar1() / 32;
340
341 diffTex->setXYWH(SkIntToScalar(kDiffXOff), gYOffs[fObjType],
342 gSizes[fObjType], gSizes[fObjType]);
343 normTex->setXYWH(SkIntToScalar(kNormXOff), gYOffs[fObjType],
344 gSizes[fObjType], gSizes[fObjType]);
345 }
346
347 void initShip(const SkRect& bounds, SkRect* diffTex, SkRect* normTex) {
348 fObjType = kShip_ObjType;
349 fPosition.set(bounds.centerX(), bounds.centerY());
350 fVelocity = SkVector::Make(0.0f, 0.0f);
351 fRot = 0.0f;
352 fDeltaRot = 0.0f;
353
354 diffTex->setXYWH(SkIntToScalar(kDiffXOff), SkIntToScalar(kShipYOff),
355 SkIntToScalar(kMedSize), SkIntToScalar(kMedSize));
356 normTex->setXYWH(SkIntToScalar(kNormXOff), SkIntToScalar(kShipYOff),
357 SkIntToScalar(kMedSize), SkIntToScalar(kMedSize));
358 }
359
360 void advance(const SkRect& bounds) {
361 fPosition += fVelocity;
362 if (fPosition.fX > bounds.right()) {
363 SkASSERT(fVelocity.fX > 0);
364 fVelocity.fX = -fVelocity.fX;
365 } else if (fPosition.fX < bounds.left()) {
366 SkASSERT(fVelocity.fX < 0);
367 fVelocity.fX = -fVelocity.fX;
368 }
369 if (fPosition.fY > bounds.bottom()) {
370 if (fVelocity.fY > 0) {
371 fVelocity.fY = -fVelocity.fY;
372 }
373 } else if (fPosition.fY < bounds.top()) {
374 if (fVelocity.fY < 0) {
375 fVelocity.fY = -fVelocity.fY;
376 }
377 }
378
379 fRot += fDeltaRot;
380 fRot = SkScalarMod(fRot, 2 * SK_ScalarPI);
381 }
382
383 const SkPoint& pos() const { return fPosition; }
384
385 SkScalar rot() const { return fRot; }
386 void setRot(SkScalar rot) { fRot = rot; }
387
388 const SkPoint& velocity() const { return fVelocity; }
389 void setVelocity(const SkPoint& velocity) { fVelocity = velocity; }
390
391 SkRSXform asRSXform() const {
392 static const SkScalar gHalfSizes[kObjTypeCount] = {
393 SkScalarHalf(kBigSize),
394 SkScalarHalf(kMedSize),
395 SkScalarHalf(kSmSize),
396 SkScalarHalf(kMedSize),
397 };
398
399 return SkRSXform::MakeFromRadians(1.0f, fRot, fPosition.x(), fPosition.y(),
400 gHalfSizes[fObjType],
401 gHalfSizes[fObjType]);
402 }
403
404 private:
405 ObjType fObjType;
406 SkPoint fPosition;
407 SkVector fVelocity;
408 SkScalar fRot; // In radians.
409 SkScalar fDeltaRot; // In radiands. Not used by ship.
410 };
411
412private:
413 static const int kNumLights = 2;
414 static const int kNumAsteroids = 6;
415 static const int kNumShips = 1;
416
417 static const int kBigSize = 128;
418 static const int kMedSize = 64;
419 static const int kSmSize = 32;
420 static const int kPad = 1;
421 static const int kAtlasWidth = kBigSize + kBigSize + 2 * kPad; // 2 pads in the middle
422 static const int kAtlasHeight = kBigSize + kMedSize + kSmSize + kMedSize + 3 * kPad;
423
424 static const int kDiffXOff = 0;
425 static const int kNormXOff = kBigSize + 2 * kPad;
426
427 static const int kBigYOff = 0;
428 static const int kMedYOff = kBigSize + kPad;
429 static const int kSmYOff = kMedYOff + kMedSize + kPad;
430 static const int kShipYOff = kSmYOff + kSmSize + kPad;
431 static const int kMaxShipSpeed = 5;
432
433 SkBitmap fAtlas;
434 ObjectRecord fAsteroids[kNumAsteroids];
435 ObjectRecord fShip;
436 SkRect fDiffTex[kNumAsteroids+kNumShips];
437 SkRect fNormTex[kNumAsteroids+kNumShips];
438 SkRect fBounds;
439 bool fUseColors;
440 SkVector3 fLightDir;
441 sk_sp<SkLights> fLights;
442
443 typedef SkDrawable INHERITED;
444};
445
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400446class DrawLitAtlasView : public Sample {
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400447public:
448 DrawLitAtlasView() : fDrawable(new DrawLitAtlasDrawable(SkRect::MakeWH(640, 480))) {}
449
450protected:
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400451 bool onQuery(Sample::Event* evt) override {
452 if (Sample::TitleQ(*evt)) {
453 Sample::TitleR(evt, "DrawLitAtlas");
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400454 return true;
455 }
456 SkUnichar uni;
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400457 if (Sample::CharQ(*evt, &uni)) {
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400458 switch (uni) {
459 case 'C':
460 fDrawable->toggleUseColors();
461 return true;
462 case 'j':
463 fDrawable->left();
464 return true;
465 case 'k':
466 fDrawable->thrust();
467 return true;
468 case 'l':
469 fDrawable->right();
470 return true;
471 case 'o':
472 fDrawable->rotateLight();
473 return true;
474 default:
475 break;
476 }
477 }
478 return this->INHERITED::onQuery(evt);
479 }
480
481 void onDrawContent(SkCanvas* canvas) override {
482 canvas->drawDrawable(fDrawable.get());
483 }
484
Mike Kleincd5104e2019-03-20 11:55:08 -0500485 bool onAnimate(const AnimTimer& timer) override { return true; }
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400486
487private:
488 sk_sp<DrawLitAtlasDrawable> fDrawable;
489
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400490 typedef Sample INHERITED;
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400491};
492
493//////////////////////////////////////////////////////////////////////////////
494
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400495DEF_SAMPLE( return new DrawLitAtlasView(); )