blob: 534b66b961b97a5a873ef8bbe124410d81d209a5 [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
Ben Wagnerb2c4ea62018-08-08 11:36:17 -04008#include "Sample.h"
Robert Phillipsa8cdbd72018-07-17 12:30:40 -04009#include "SkAnimTimer.h"
10#include "SkBitmapProcShader.h"
11#include "SkCanvas.h"
12#include "SkDrawable.h"
13#include "SkLightingShader.h"
14#include "SkLights.h"
15#include "SkNormalSource.h"
16#include "SkRandom.h"
17#include "SkRSXform.h"
Robert Phillipsa8cdbd72018-07-17 12:30:40 -040018
19#include "sk_tool_utils.h"
20
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
254 sk_tool_utils::create_hemi_normal_map(&atlas,
255 SkIRect::MakeXYWH(kNormXOff, kBigYOff,
256 kBigSize, kBigSize));
257 }
258
259 // medium asteroid
260 {
261 for (int y = kMedYOff; y < kMedYOff+kMedSize; ++y) {
262 for (int x = kDiffXOff; x < kDiffXOff+kMedSize; ++x) {
263 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0xFF, 0);
264 }
265 }
266
267 sk_tool_utils::create_frustum_normal_map(&atlas,
268 SkIRect::MakeXYWH(kNormXOff, kMedYOff,
269 kMedSize, kMedSize));
270 }
271
272 // small asteroid
273 {
274 SkPoint smCenter = SkPoint::Make(kDiffXOff + kSmSize/2.0f, kSmYOff + kSmSize/2.0f);
275
276 for (int y = kSmYOff; y < kSmYOff+kSmSize; ++y) {
277 for (int x = kDiffXOff; x < kDiffXOff+kSmSize; ++x) {
278 SkScalar distSq = (x - smCenter.fX) * (x - smCenter.fX) +
279 (y - smCenter.fY) * (y - smCenter.fY);
280 if (distSq > kSmSize*kSmSize/4.0f) {
281 *atlas.getAddr32(x, y) = SkPreMultiplyARGB(0, 0, 0, 0);
282 } else {
283 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0, 0xFF);
284 }
285 }
286 }
287
288 sk_tool_utils::create_hemi_normal_map(&atlas,
289 SkIRect::MakeXYWH(kNormXOff, kSmYOff,
290 kSmSize, kSmSize));
291 }
292
293 // ship
294 {
295 SkScalar shipMidLine = kDiffXOff + kMedSize/2.0f;
296
297 for (int y = kShipYOff; y < kShipYOff+kMedSize; ++y) {
298 SkScalar scaledY = (y - kShipYOff)/(float)kMedSize; // 0..1
299
300 for (int x = kDiffXOff; x < kDiffXOff+kMedSize; ++x) {
301 SkScalar scaledX;
302
303 if (x < shipMidLine) {
304 scaledX = 1.0f - (x - kDiffXOff)/(kMedSize/2.0f); // 0..1
305 } else {
306 scaledX = (x - shipMidLine)/(kMedSize/2.0f); // 0..1
307 }
308
309 if (scaledX < scaledY) {
310 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0xFF, 0xFF);
311 } else {
312 *atlas.getAddr32(x, y) = SkPackARGB32(0, 0, 0, 0);
313 }
314 }
315 }
316
317 sk_tool_utils::create_tetra_normal_map(&atlas,
318 SkIRect::MakeXYWH(kNormXOff, kShipYOff,
319 kMedSize, kMedSize));
320 }
321
322 return atlas;
323 }
324
325 class ObjectRecord {
326 public:
327 void initAsteroid(SkRandom *rand, const SkRect& bounds,
328 SkRect* diffTex, SkRect* normTex) {
329 static const SkScalar gMaxSpeeds[3] = { 1, 2, 5 }; // smaller asteroids can go faster
330 static const SkScalar gYOffs[3] = { kBigYOff, kMedYOff, kSmYOff };
331 static const SkScalar gSizes[3] = { kBigSize, kMedSize, kSmSize };
332
333 static unsigned int asteroidType = 0;
334 fObjType = static_cast<ObjType>(asteroidType++ % 3);
335
336 fPosition.set(bounds.fLeft + rand->nextUScalar1() * bounds.width(),
337 bounds.fTop + rand->nextUScalar1() * bounds.height());
338 fVelocity.fX = rand->nextSScalar1();
339 fVelocity.fY = sqrt(1.0f - fVelocity.fX * fVelocity.fX);
340 SkASSERT(SkScalarNearlyEqual(fVelocity.length(), 1.0f));
341 fVelocity *= gMaxSpeeds[fObjType];
342 fRot = 0;
343 fDeltaRot = rand->nextSScalar1() / 32;
344
345 diffTex->setXYWH(SkIntToScalar(kDiffXOff), gYOffs[fObjType],
346 gSizes[fObjType], gSizes[fObjType]);
347 normTex->setXYWH(SkIntToScalar(kNormXOff), gYOffs[fObjType],
348 gSizes[fObjType], gSizes[fObjType]);
349 }
350
351 void initShip(const SkRect& bounds, SkRect* diffTex, SkRect* normTex) {
352 fObjType = kShip_ObjType;
353 fPosition.set(bounds.centerX(), bounds.centerY());
354 fVelocity = SkVector::Make(0.0f, 0.0f);
355 fRot = 0.0f;
356 fDeltaRot = 0.0f;
357
358 diffTex->setXYWH(SkIntToScalar(kDiffXOff), SkIntToScalar(kShipYOff),
359 SkIntToScalar(kMedSize), SkIntToScalar(kMedSize));
360 normTex->setXYWH(SkIntToScalar(kNormXOff), SkIntToScalar(kShipYOff),
361 SkIntToScalar(kMedSize), SkIntToScalar(kMedSize));
362 }
363
364 void advance(const SkRect& bounds) {
365 fPosition += fVelocity;
366 if (fPosition.fX > bounds.right()) {
367 SkASSERT(fVelocity.fX > 0);
368 fVelocity.fX = -fVelocity.fX;
369 } else if (fPosition.fX < bounds.left()) {
370 SkASSERT(fVelocity.fX < 0);
371 fVelocity.fX = -fVelocity.fX;
372 }
373 if (fPosition.fY > bounds.bottom()) {
374 if (fVelocity.fY > 0) {
375 fVelocity.fY = -fVelocity.fY;
376 }
377 } else if (fPosition.fY < bounds.top()) {
378 if (fVelocity.fY < 0) {
379 fVelocity.fY = -fVelocity.fY;
380 }
381 }
382
383 fRot += fDeltaRot;
384 fRot = SkScalarMod(fRot, 2 * SK_ScalarPI);
385 }
386
387 const SkPoint& pos() const { return fPosition; }
388
389 SkScalar rot() const { return fRot; }
390 void setRot(SkScalar rot) { fRot = rot; }
391
392 const SkPoint& velocity() const { return fVelocity; }
393 void setVelocity(const SkPoint& velocity) { fVelocity = velocity; }
394
395 SkRSXform asRSXform() const {
396 static const SkScalar gHalfSizes[kObjTypeCount] = {
397 SkScalarHalf(kBigSize),
398 SkScalarHalf(kMedSize),
399 SkScalarHalf(kSmSize),
400 SkScalarHalf(kMedSize),
401 };
402
403 return SkRSXform::MakeFromRadians(1.0f, fRot, fPosition.x(), fPosition.y(),
404 gHalfSizes[fObjType],
405 gHalfSizes[fObjType]);
406 }
407
408 private:
409 ObjType fObjType;
410 SkPoint fPosition;
411 SkVector fVelocity;
412 SkScalar fRot; // In radians.
413 SkScalar fDeltaRot; // In radiands. Not used by ship.
414 };
415
416private:
417 static const int kNumLights = 2;
418 static const int kNumAsteroids = 6;
419 static const int kNumShips = 1;
420
421 static const int kBigSize = 128;
422 static const int kMedSize = 64;
423 static const int kSmSize = 32;
424 static const int kPad = 1;
425 static const int kAtlasWidth = kBigSize + kBigSize + 2 * kPad; // 2 pads in the middle
426 static const int kAtlasHeight = kBigSize + kMedSize + kSmSize + kMedSize + 3 * kPad;
427
428 static const int kDiffXOff = 0;
429 static const int kNormXOff = kBigSize + 2 * kPad;
430
431 static const int kBigYOff = 0;
432 static const int kMedYOff = kBigSize + kPad;
433 static const int kSmYOff = kMedYOff + kMedSize + kPad;
434 static const int kShipYOff = kSmYOff + kSmSize + kPad;
435 static const int kMaxShipSpeed = 5;
436
437 SkBitmap fAtlas;
438 ObjectRecord fAsteroids[kNumAsteroids];
439 ObjectRecord fShip;
440 SkRect fDiffTex[kNumAsteroids+kNumShips];
441 SkRect fNormTex[kNumAsteroids+kNumShips];
442 SkRect fBounds;
443 bool fUseColors;
444 SkVector3 fLightDir;
445 sk_sp<SkLights> fLights;
446
447 typedef SkDrawable INHERITED;
448};
449
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400450class DrawLitAtlasView : public Sample {
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400451public:
452 DrawLitAtlasView() : fDrawable(new DrawLitAtlasDrawable(SkRect::MakeWH(640, 480))) {}
453
454protected:
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400455 bool onQuery(Sample::Event* evt) override {
456 if (Sample::TitleQ(*evt)) {
457 Sample::TitleR(evt, "DrawLitAtlas");
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400458 return true;
459 }
460 SkUnichar uni;
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400461 if (Sample::CharQ(*evt, &uni)) {
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400462 switch (uni) {
463 case 'C':
464 fDrawable->toggleUseColors();
465 return true;
466 case 'j':
467 fDrawable->left();
468 return true;
469 case 'k':
470 fDrawable->thrust();
471 return true;
472 case 'l':
473 fDrawable->right();
474 return true;
475 case 'o':
476 fDrawable->rotateLight();
477 return true;
478 default:
479 break;
480 }
481 }
482 return this->INHERITED::onQuery(evt);
483 }
484
485 void onDrawContent(SkCanvas* canvas) override {
486 canvas->drawDrawable(fDrawable.get());
487 }
488
489 bool onAnimate(const SkAnimTimer& timer) override {
490 return true;
491 }
492
493private:
494 sk_sp<DrawLitAtlasDrawable> fDrawable;
495
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400496 typedef Sample INHERITED;
Robert Phillipsa8cdbd72018-07-17 12:30:40 -0400497};
498
499//////////////////////////////////////////////////////////////////////////////
500
Ben Wagnerb2c4ea62018-08-08 11:36:17 -0400501DEF_SAMPLE( return new DrawLitAtlasView(); )