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