blob: f1d67e5c7933b0050e491e51c0d974898e810268 [file] [log] [blame]
robertphillipsadf5afa2016-06-03 10:12:08 -07001/*
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
8#include "SampleCode.h"
9#include "SkAnimTimer.h"
10#include "SkView.h"
11#include "SkCanvas.h"
12#include "SkDrawable.h"
13#include "SkLightingShader.h"
14#include "SkLights.h"
15#include "SkRandom.h"
16#include "SkRSXform.h"
17
18#include "sk_tool_utils.h"
19
20class DrawLitAtlasDrawable : public SkDrawable {
21public:
22 DrawLitAtlasDrawable(const SkRect& r)
23 : fBounds(r)
robertphillips862dae52016-06-03 15:48:16 -070024 , fUseColors(false)
25 , fLightDir(SkVector3::Make(1.0f, 0.0f, 0.0f)) {
robertphillipsadf5afa2016-06-03 10:12:08 -070026 fAtlas = MakeAtlas();
27
28 SkRandom rand;
29 for (int i = 0; i < kNumAsteroids; ++i) {
30 fAsteroids[i].initAsteroid(&rand, fBounds, &fDiffTex[i], &fNormTex[i]);
31 }
32
33 fShip.initShip(fBounds, &fDiffTex[kNumAsteroids], &fNormTex[kNumAsteroids]);
34
robertphillips862dae52016-06-03 15:48:16 -070035 this->updateLights();
robertphillipsadf5afa2016-06-03 10:12:08 -070036 }
37
38 void toggleUseColors() {
39 fUseColors = !fUseColors;
40 }
41
robertphillips862dae52016-06-03 15:48:16 -070042 void rotateLight() {
43 SkScalar c;
44 SkScalar s = SkScalarSinCos(SK_ScalarPI/6.0f, &c);
45
46 SkScalar newX = c * fLightDir.fX - s * fLightDir.fY;
47 SkScalar newY = s * fLightDir.fX + c * fLightDir.fY;
48
49 fLightDir.set(newX, newY, 0.0f);
50
51 this->updateLights();
52 }
53
robertphillipsadf5afa2016-06-03 10:12:08 -070054 void left() {
55 SkScalar newRot = SkScalarMod(fShip.rot() + (2*SK_ScalarPI - SK_ScalarPI/32.0f),
56 2 * SK_ScalarPI);
57 fShip.setRot(newRot);
58 }
59
60 void right() {
61 SkScalar newRot = SkScalarMod(fShip.rot() + SK_ScalarPI/32.0f, 2 * SK_ScalarPI);
62 fShip.setRot(newRot);
63 }
64
65 void thrust() {
66 SkScalar c;
67 SkScalar s = SkScalarSinCos(fShip.rot(), &c);
68
69 SkVector newVel = fShip.velocity();
70 newVel.fX += s;
71 newVel.fY += -c;
72
73 if (newVel.lengthSqd() > kMaxShipSpeed*kMaxShipSpeed) {
74 newVel.setLength(SkIntToScalar(kMaxShipSpeed));
75 }
76
77 fShip.setVelocity(newVel);
78 }
79
80protected:
81 void onDraw(SkCanvas* canvas) override {
82 SkRSXform xforms[kNumAsteroids+kNumShips];
83 SkColor colors[kNumAsteroids+kNumShips];
84
85 for (int i = 0; i < kNumAsteroids; ++i) {
86 fAsteroids[i].advance(fBounds);
87 xforms[i] = fAsteroids[i].asRSXform();
88 if (fUseColors) {
89 colors[i] = SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF);
90 }
91 }
92
93 fShip.advance(fBounds);
94 xforms[kNumAsteroids] = fShip.asRSXform();
95 if (fUseColors) {
96 colors[kNumAsteroids] = SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF);
97 }
98
99#ifdef SK_DEBUG
100 canvas->drawBitmap(fAtlas, 0, 0); // just to see the atlas
robertphillips862dae52016-06-03 15:48:16 -0700101
102 this->drawLightDir(canvas, fBounds.centerX(), fBounds.centerY());
103#endif
robertphillipsadf5afa2016-06-03 10:12:08 -0700104
105#if 0
106 // TODO: revitalize when drawLitAtlas API lands
107 SkPaint paint;
108 paint.setFilterQuality(kLow_SkFilterQuality);
109
110 const SkRect cull = this->getBounds();
111 const SkColor* colorsPtr = fUseColors ? colors : NULL;
112
113 canvas->drawLitAtlas(fAtlas, xforms, fDiffTex, fNormTex, colorsPtr, kNumAsteroids+1,
114 SkXfermode::kModulate_Mode, &cull, &paint, fLights);
115#else
116 SkMatrix diffMat, normalMat;
117
118 for (int i = 0; i < kNumAsteroids+1; ++i) {
119 colors[i] = colors[i] & 0xFF000000; // to silence compilers
120 SkPaint paint;
121
122 SkRect r = fDiffTex[i];
123 r.offsetTo(0, 0);
124
125 diffMat.setRectToRect(fDiffTex[i], r, SkMatrix::kFill_ScaleToFit);
126 normalMat.setRectToRect(fNormTex[i], r, SkMatrix::kFill_ScaleToFit);
127
128 SkMatrix m;
129 m.setRSXform(xforms[i]);
130
131 // TODO: correctly pull out the pure rotation
132 SkVector invNormRotation = { m[SkMatrix::kMScaleX], m[SkMatrix::kMSkewY] };
robertphillips862dae52016-06-03 15:48:16 -0700133 SkASSERT(SkScalarNearlyEqual(invNormRotation.lengthSqd(), SK_Scalar1));
robertphillipsadf5afa2016-06-03 10:12:08 -0700134
135 paint.setShader(SkLightingShader::Make(fAtlas, fAtlas, fLights,
136 invNormRotation, &diffMat, &normalMat));
137
138 canvas->save();
139 canvas->setMatrix(m);
140 canvas->drawRect(r, paint);
141 canvas->restore();
142 }
143#endif
144
145#ifdef SK_DEBUG
146 {
147 SkPaint paint;
148 paint.setColor(SK_ColorRED);
149
150 for (int i = 0; i < kNumAsteroids; ++i) {
151 canvas->drawCircle(fAsteroids[i].pos().x(), fAsteroids[i].pos().y(), 2, paint);
152 }
153 canvas->drawCircle(fShip.pos().x(), fShip.pos().y(), 2, paint);
154
155 paint.setStyle(SkPaint::kStroke_Style);
156 canvas->drawRect(this->getBounds(), paint);
157 }
158#endif
159 }
160
161 SkRect onGetBounds() override {
162 return fBounds;
163 }
164
165private:
166
167 enum ObjType {
168 kBigAsteroid_ObjType = 0,
169 kMedAsteroid_ObjType,
170 kSmAsteroid_ObjType,
171 kShip_ObjType,
172
173 kLast_ObjType = kShip_ObjType
174 };
175
176 static const int kObjTypeCount = kLast_ObjType + 1;
177
robertphillips862dae52016-06-03 15:48:16 -0700178 void updateLights() {
179 SkLights::Builder builder;
180
181 builder.add(SkLights::Light(SkColor3f::Make(1.0f, 1.0f, 1.0f), fLightDir));
182 builder.add(SkLights::Light(SkColor3f::Make(0.2f, 0.2f, 0.2f)));
183
184 fLights = builder.finish();
185 }
186
187#ifdef SK_DEBUG
188 // Draw a vector to the light
189 void drawLightDir(SkCanvas* canvas, SkScalar centerX, SkScalar centerY) {
190 static const int kBgLen = 30;
191 static const int kSmLen = 5;
192
193 // TODO: change the lighting coordinate system to be right handed
194 SkPoint p1 = SkPoint::Make(centerX + kBgLen * fLightDir.fX,
195 centerY - kBgLen * fLightDir.fY);
196 SkPoint p2 = SkPoint::Make(centerX + (kBgLen-kSmLen) * fLightDir.fX,
197 centerY - (kBgLen-kSmLen) * fLightDir.fY);
198
199 SkPaint p;
200 canvas->drawLine(centerX, centerY, p1.fX, p1.fY, p);
201 canvas->drawLine(p1.fX, p1.fY,
202 p2.fX - kSmLen * fLightDir.fY, p2.fY - kSmLen * fLightDir.fX, p);
203 canvas->drawLine(p1.fX, p1.fY,
204 p2.fX + kSmLen * fLightDir.fY, p2.fY + kSmLen * fLightDir.fX, p);
205 }
206#endif
207
robertphillipsadf5afa2016-06-03 10:12:08 -0700208 // Create the mixed diffuse & normal atlas
209 //
210 // big color circle | big normal hemi
211 // ------------------------------------
212 // med color circle | med normal pyra
213 // ------------------------------------
214 // sm color circle | sm normal hemi
215 // ------------------------------------
216 // big ship | big tetra normal
217 static SkBitmap MakeAtlas() {
218
219 SkBitmap atlas;
220 atlas.allocN32Pixels(kAtlasWidth, kAtlasHeight);
221
222 for (int y = 0; y < kAtlasHeight; ++y) {
223 int x = 0;
224 for ( ; x < kBigSize+kPad; ++x) {
225 *atlas.getAddr32(x, y) = SK_ColorTRANSPARENT;
226 }
227 for ( ; x < kAtlasWidth; ++x) {
228 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0x88, 0x88, 0xFF);
229 }
230 }
231
232 // big asteroid
233 {
234 SkPoint bigCenter = SkPoint::Make(kDiffXOff + kBigSize/2.0f, kBigYOff + kBigSize/2.0f);
235
236 for (int y = kBigYOff; y < kBigYOff+kBigSize; ++y) {
237 for (int x = kDiffXOff; x < kDiffXOff+kBigSize; ++x) {
238 SkScalar distSq = (x - bigCenter.fX) * (x - bigCenter.fX) +
239 (y - bigCenter.fY) * (y - bigCenter.fY);
240 if (distSq > kBigSize*kBigSize/4.0f) {
241 *atlas.getAddr32(x, y) = SkPreMultiplyARGB(0, 0, 0, 0);
242 } else {
243 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0xFF, 0, 0);
244 }
245 }
246 }
247
248 sk_tool_utils::create_hemi_normal_map(&atlas,
249 SkIRect::MakeXYWH(kNormXOff, kBigYOff,
250 kBigSize, kBigSize));
251 }
252
253 // medium asteroid
254 {
255 for (int y = kMedYOff; y < kMedYOff+kMedSize; ++y) {
256 for (int x = kDiffXOff; x < kDiffXOff+kMedSize; ++x) {
257 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0xFF, 0);
258 }
259 }
260
261 sk_tool_utils::create_frustum_normal_map(&atlas,
262 SkIRect::MakeXYWH(kNormXOff, kMedYOff,
263 kMedSize, kMedSize));
264 }
265
266 // small asteroid
267 {
268 SkPoint smCenter = SkPoint::Make(kDiffXOff + kSmSize/2.0f, kSmYOff + kSmSize/2.0f);
269
270 for (int y = kSmYOff; y < kSmYOff+kSmSize; ++y) {
271 for (int x = kDiffXOff; x < kDiffXOff+kSmSize; ++x) {
272 SkScalar distSq = (x - smCenter.fX) * (x - smCenter.fX) +
273 (y - smCenter.fY) * (y - smCenter.fY);
274 if (distSq > kSmSize*kSmSize/4.0f) {
275 *atlas.getAddr32(x, y) = SkPreMultiplyARGB(0, 0, 0, 0);
276 } else {
277 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0, 0xFF);
278 }
279 }
280 }
281
282 sk_tool_utils::create_hemi_normal_map(&atlas,
283 SkIRect::MakeXYWH(kNormXOff, kSmYOff,
284 kSmSize, kSmSize));
285 }
286
287 // ship
288 {
289 SkScalar shipMidLine = kDiffXOff + kMedSize/2.0f;
290
291 for (int y = kShipYOff; y < kShipYOff+kMedSize; ++y) {
292 SkScalar scaledY = (y - kShipYOff)/(float)kMedSize; // 0..1
293
294 for (int x = kDiffXOff; x < kDiffXOff+kMedSize; ++x) {
295 SkScalar scaledX;
296
297 if (x < shipMidLine) {
298 scaledX = 1.0f - (x - kDiffXOff)/(kMedSize/2.0f); // 0..1
299 } else {
300 scaledX = (x - shipMidLine)/(kMedSize/2.0f); // 0..1
301 }
302
303 if (scaledX < scaledY) {
304 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0xFF, 0xFF);
305 } else {
306 *atlas.getAddr32(x, y) = SkPackARGB32(0, 0, 0, 0);
307 }
308 }
309 }
310
311 sk_tool_utils::create_tetra_normal_map(&atlas,
312 SkIRect::MakeXYWH(kNormXOff, kShipYOff,
313 kMedSize, kMedSize));
314 }
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
410
411
412
413private:
414 static const int kNumLights = 2;
415 static const int kNumAsteroids = 6;
416 static const int kNumShips = 1;
417
418 static const int kBigSize = 128;
419 static const int kMedSize = 64;
420 static const int kSmSize = 32;
421 static const int kPad = 1;
422 static const int kAtlasWidth = kBigSize + kBigSize + 2 * kPad; // 2 pads in the middle
423 static const int kAtlasHeight = kBigSize + kMedSize + kSmSize + kMedSize + 3 * kPad;
424
425 static const int kDiffXOff = 0;
426 static const int kNormXOff = kBigSize + 2 * kPad;
427
428 static const int kBigYOff = 0;
429 static const int kMedYOff = kBigSize + kPad;
430 static const int kSmYOff = kMedYOff + kMedSize + kPad;
431 static const int kShipYOff = kSmYOff + kSmSize + kPad;
432 static const int kMaxShipSpeed = 5;
433
434 SkBitmap fAtlas;
435 ObjectRecord fAsteroids[kNumAsteroids];
436 ObjectRecord fShip;
437 SkRect fDiffTex[kNumAsteroids+kNumShips];
438 SkRect fNormTex[kNumAsteroids+kNumShips];
439 SkRect fBounds;
440 bool fUseColors;
robertphillips862dae52016-06-03 15:48:16 -0700441 SkVector3 fLightDir;
robertphillipsadf5afa2016-06-03 10:12:08 -0700442 sk_sp<SkLights> fLights;
443
444 typedef SkDrawable INHERITED;
445};
446
447class DrawLitAtlasView : public SampleView {
448public:
449 DrawLitAtlasView()
450 : fDrawable(new DrawLitAtlasDrawable(SkRect::MakeWH(640, 480))) {
451 }
452
453protected:
454 bool onQuery(SkEvent* evt) override {
455 if (SampleCode::TitleQ(*evt)) {
456 SampleCode::TitleR(evt, "DrawLitAtlas");
457 return true;
458 }
459 SkUnichar uni;
460 if (SampleCode::CharQ(*evt, &uni)) {
461 switch (uni) {
462 case 'C':
463 fDrawable->toggleUseColors();
464 this->inval(NULL);
465 return true;
466 case 'j':
467 fDrawable->left();
468 this->inval(NULL);
469 return true;
470 case 'k':
471 fDrawable->thrust();
472 this->inval(NULL);
473 return true;
474 case 'l':
475 fDrawable->right();
476 this->inval(NULL);
477 return true;
robertphillips862dae52016-06-03 15:48:16 -0700478 case 'o':
479 fDrawable->rotateLight();
480 this->inval(NULL);
481 return true;
robertphillipsadf5afa2016-06-03 10:12:08 -0700482 default:
483 break;
484 }
485 }
486 return this->INHERITED::onQuery(evt);
487 }
488
489 void onDrawContent(SkCanvas* canvas) override {
490 canvas->drawDrawable(fDrawable);
491 this->inval(NULL);
492 }
493
494#if 0
495 // TODO: switch over to use this for our animation
496 bool onAnimate(const SkAnimTimer& timer) override {
497 SkScalar angle = SkDoubleToScalar(fmod(timer.secs() * 360 / 24, 360));
498 fAnimatingDrawable->setSweep(angle);
499 return true;
500 }
501#endif
502
503private:
504 SkAutoTUnref<DrawLitAtlasDrawable> fDrawable;
505
506 typedef SampleView INHERITED;
507};
508
509//////////////////////////////////////////////////////////////////////////////
510
511static SkView* MyFactory() { return new DrawLitAtlasView; }
512static SkViewRegister reg(MyFactory);