1 /* 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 "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" 18 #include "SkView.h" 19 20 #include "sk_tool_utils.h" 21 22 class DrawLitAtlasDrawable : public SkDrawable { 23 public: 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 if (newVel.lengthSqd() > kMaxShipSpeed*kMaxShipSpeed) { 76 newVel.setLength(SkIntToScalar(kMaxShipSpeed)); 77 } 78 79 fShip.setVelocity(newVel); 80 } 81 82 protected: 83 void onDraw(SkCanvas* canvas) override { 84 SkRSXform xforms[kNumAsteroids+kNumShips]; 85 SkColor colors[kNumAsteroids+kNumShips]; 86 87 for (int i = 0; i < kNumAsteroids; ++i) { 88 fAsteroids[i].advance(fBounds); 89 xforms[i] = fAsteroids[i].asRSXform(); 90 if (fUseColors) { 91 colors[i] = SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF); 92 } 93 } 94 95 fShip.advance(fBounds); 96 xforms[kNumAsteroids] = fShip.asRSXform(); 97 if (fUseColors) { 98 colors[kNumAsteroids] = SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF); 99 } 100 101 #ifdef SK_DEBUG 102 canvas->drawBitmap(fAtlas, 0, 0); // just to see the atlas 103 104 this->drawLightDir(canvas, fBounds.centerX(), fBounds.centerY()); 105 #endif 106 107 #if 0 108 // TODO: revitalize when drawLitAtlas API lands 109 SkPaint paint; 110 paint.setFilterQuality(kLow_SkFilterQuality); 111 112 const SkRect cull = this->getBounds(); 113 const SkColor* colorsPtr = fUseColors ? colors : NULL; 114 115 canvas->drawLitAtlas(fAtlas, xforms, fDiffTex, fNormTex, colorsPtr, kNumAsteroids+1, 116 SkXfermode::kModulate_Mode, &cull, &paint, fLights); 117 #else 118 SkMatrix diffMat, normalMat; 119 120 for (int i = 0; i < kNumAsteroids+1; ++i) { 121 colors[i] = colors[i] & 0xFF000000; // to silence compilers 122 SkPaint paint; 123 124 SkRect r = fDiffTex[i]; 125 r.offsetTo(0, 0); 126 127 diffMat.setRectToRect(fDiffTex[i], r, SkMatrix::kFill_ScaleToFit); 128 normalMat.setRectToRect(fNormTex[i], r, SkMatrix::kFill_ScaleToFit); 129 130 SkMatrix m; 131 m.setRSXform(xforms[i]); 132 133 sk_sp<SkShader> normalMap = SkShader::MakeBitmapShader(fAtlas, SkShader::kClamp_TileMode, 134 SkShader::kClamp_TileMode, &normalMat); 135 sk_sp<SkNormalSource> normalSource = SkNormalSource::MakeFromNormalMap( 136 std::move(normalMap), m); 137 sk_sp<SkShader> diffuseShader = SkShader::MakeBitmapShader(fAtlas, 138 SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, &diffMat); 139 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 169 private: 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 253 sk_tool_utils::create_hemi_normal_map(&atlas, 254 SkIRect::MakeXYWH(kNormXOff, kBigYOff, 255 kBigSize, kBigSize)); 256 } 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 266 sk_tool_utils::create_frustum_normal_map(&atlas, 267 SkIRect::MakeXYWH(kNormXOff, kMedYOff, 268 kMedSize, kMedSize)); 269 } 270 271 // small asteroid 272 { 273 SkPoint smCenter = SkPoint::Make(kDiffXOff + kSmSize/2.0f, kSmYOff + kSmSize/2.0f); 274 275 for (int y = kSmYOff; y < kSmYOff+kSmSize; ++y) { 276 for (int x = kDiffXOff; x < kDiffXOff+kSmSize; ++x) { 277 SkScalar distSq = (x - smCenter.fX) * (x - smCenter.fX) + 278 (y - smCenter.fY) * (y - smCenter.fY); 279 if (distSq > kSmSize*kSmSize/4.0f) { 280 *atlas.getAddr32(x, y) = SkPreMultiplyARGB(0, 0, 0, 0); 281 } else { 282 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0, 0xFF); 283 } 284 } 285 } 286 287 sk_tool_utils::create_hemi_normal_map(&atlas, 288 SkIRect::MakeXYWH(kNormXOff, kSmYOff, 289 kSmSize, kSmSize)); 290 } 291 292 // ship 293 { 294 SkScalar shipMidLine = kDiffXOff + kMedSize/2.0f; 295 296 for (int y = kShipYOff; y < kShipYOff+kMedSize; ++y) { 297 SkScalar scaledY = (y - kShipYOff)/(float)kMedSize; // 0..1 298 299 for (int x = kDiffXOff; x < kDiffXOff+kMedSize; ++x) { 300 SkScalar scaledX; 301 302 if (x < shipMidLine) { 303 scaledX = 1.0f - (x - kDiffXOff)/(kMedSize/2.0f); // 0..1 304 } else { 305 scaledX = (x - shipMidLine)/(kMedSize/2.0f); // 0..1 306 } 307 308 if (scaledX < scaledY) { 309 *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0xFF, 0xFF); 310 } else { 311 *atlas.getAddr32(x, y) = SkPackARGB32(0, 0, 0, 0); 312 } 313 } 314 } 315 316 sk_tool_utils::create_tetra_normal_map(&atlas, 317 SkIRect::MakeXYWH(kNormXOff, kShipYOff, 318 kMedSize, kMedSize)); 319 } 320 321 return atlas; 322 } 323 324 class ObjectRecord { 325 public: 326 void initAsteroid(SkRandom *rand, const SkRect& bounds, 327 SkRect* diffTex, SkRect* normTex) { 328 static const SkScalar gMaxSpeeds[3] = { 1, 2, 5 }; // smaller asteroids can go faster 329 static const SkScalar gYOffs[3] = { kBigYOff, kMedYOff, kSmYOff }; 330 static const SkScalar gSizes[3] = { kBigSize, kMedSize, kSmSize }; 331 332 static unsigned int asteroidType = 0; 333 fObjType = static_cast<ObjType>(asteroidType++ % 3); 334 335 fPosition.set(bounds.fLeft + rand->nextUScalar1() * bounds.width(), 336 bounds.fTop + rand->nextUScalar1() * bounds.height()); 337 fVelocity.fX = rand->nextSScalar1(); 338 fVelocity.fY = sqrt(1.0f - fVelocity.fX * fVelocity.fX); 339 SkASSERT(SkScalarNearlyEqual(fVelocity.length(), 1.0f)); 340 fVelocity *= gMaxSpeeds[fObjType]; 341 fRot = 0; 342 fDeltaRot = rand->nextSScalar1() / 32; 343 344 diffTex->setXYWH(SkIntToScalar(kDiffXOff), gYOffs[fObjType], 345 gSizes[fObjType], gSizes[fObjType]); 346 normTex->setXYWH(SkIntToScalar(kNormXOff), gYOffs[fObjType], 347 gSizes[fObjType], gSizes[fObjType]); 348 } 349 350 void initShip(const SkRect& bounds, SkRect* diffTex, SkRect* normTex) { 351 fObjType = kShip_ObjType; 352 fPosition.set(bounds.centerX(), bounds.centerY()); 353 fVelocity = SkVector::Make(0.0f, 0.0f); 354 fRot = 0.0f; 355 fDeltaRot = 0.0f; 356 357 diffTex->setXYWH(SkIntToScalar(kDiffXOff), SkIntToScalar(kShipYOff), 358 SkIntToScalar(kMedSize), SkIntToScalar(kMedSize)); 359 normTex->setXYWH(SkIntToScalar(kNormXOff), SkIntToScalar(kShipYOff), 360 SkIntToScalar(kMedSize), SkIntToScalar(kMedSize)); 361 } 362 363 void advance(const SkRect& bounds) { 364 fPosition += fVelocity; 365 if (fPosition.fX > bounds.right()) { 366 SkASSERT(fVelocity.fX > 0); 367 fVelocity.fX = -fVelocity.fX; 368 } else if (fPosition.fX < bounds.left()) { 369 SkASSERT(fVelocity.fX < 0); 370 fVelocity.fX = -fVelocity.fX; 371 } 372 if (fPosition.fY > bounds.bottom()) { 373 if (fVelocity.fY > 0) { 374 fVelocity.fY = -fVelocity.fY; 375 } 376 } else if (fPosition.fY < bounds.top()) { 377 if (fVelocity.fY < 0) { 378 fVelocity.fY = -fVelocity.fY; 379 } 380 } 381 382 fRot += fDeltaRot; 383 fRot = SkScalarMod(fRot, 2 * SK_ScalarPI); 384 } 385 386 const SkPoint& pos() const { return fPosition; } 387 388 SkScalar rot() const { return fRot; } 389 void setRot(SkScalar rot) { fRot = rot; } 390 391 const SkPoint& velocity() const { return fVelocity; } 392 void setVelocity(const SkPoint& velocity) { fVelocity = velocity; } 393 394 SkRSXform asRSXform() const { 395 static const SkScalar gHalfSizes[kObjTypeCount] = { 396 SkScalarHalf(kBigSize), 397 SkScalarHalf(kMedSize), 398 SkScalarHalf(kSmSize), 399 SkScalarHalf(kMedSize), 400 }; 401 402 return SkRSXform::MakeFromRadians(1.0f, fRot, fPosition.x(), fPosition.y(), 403 gHalfSizes[fObjType], 404 gHalfSizes[fObjType]); 405 } 406 407 private: 408 ObjType fObjType; 409 SkPoint fPosition; 410 SkVector fVelocity; 411 SkScalar fRot; // In radians. 412 SkScalar fDeltaRot; // In radiands. Not used by ship. 413 }; 414 415 416 417 418 private: 419 static const int kNumLights = 2; 420 static const int kNumAsteroids = 6; 421 static const int kNumShips = 1; 422 423 static const int kBigSize = 128; 424 static const int kMedSize = 64; 425 static const int kSmSize = 32; 426 static const int kPad = 1; 427 static const int kAtlasWidth = kBigSize + kBigSize + 2 * kPad; // 2 pads in the middle 428 static const int kAtlasHeight = kBigSize + kMedSize + kSmSize + kMedSize + 3 * kPad; 429 430 static const int kDiffXOff = 0; 431 static const int kNormXOff = kBigSize + 2 * kPad; 432 433 static const int kBigYOff = 0; 434 static const int kMedYOff = kBigSize + kPad; 435 static const int kSmYOff = kMedYOff + kMedSize + kPad; 436 static const int kShipYOff = kSmYOff + kSmSize + kPad; 437 static const int kMaxShipSpeed = 5; 438 439 SkBitmap fAtlas; 440 ObjectRecord fAsteroids[kNumAsteroids]; 441 ObjectRecord fShip; 442 SkRect fDiffTex[kNumAsteroids+kNumShips]; 443 SkRect fNormTex[kNumAsteroids+kNumShips]; 444 SkRect fBounds; 445 bool fUseColors; 446 SkVector3 fLightDir; 447 sk_sp<SkLights> fLights; 448 449 typedef SkDrawable INHERITED; 450 }; 451 452 class DrawLitAtlasView : public SampleView { 453 public: 454 DrawLitAtlasView() 455 : fDrawable(new DrawLitAtlasDrawable(SkRect::MakeWH(640, 480))) { 456 } 457 458 protected: 459 bool onQuery(SkEvent* evt) override { 460 if (SampleCode::TitleQ(*evt)) { 461 SampleCode::TitleR(evt, "DrawLitAtlas"); 462 return true; 463 } 464 SkUnichar uni; 465 if (SampleCode::CharQ(*evt, &uni)) { 466 switch (uni) { 467 case 'C': 468 fDrawable->toggleUseColors(); 469 this->inval(NULL); 470 return true; 471 case 'j': 472 fDrawable->left(); 473 this->inval(NULL); 474 return true; 475 case 'k': 476 fDrawable->thrust(); 477 this->inval(NULL); 478 return true; 479 case 'l': 480 fDrawable->right(); 481 this->inval(NULL); 482 return true; 483 case 'o': 484 fDrawable->rotateLight(); 485 this->inval(NULL); 486 return true; 487 default: 488 break; 489 } 490 } 491 return this->INHERITED::onQuery(evt); 492 } 493 494 void onDrawContent(SkCanvas* canvas) override { 495 canvas->drawDrawable(fDrawable.get()); 496 this->inval(NULL); 497 } 498 499 #if 0 500 // TODO: switch over to use this for our animation 501 bool onAnimate(const SkAnimTimer& timer) override { 502 SkScalar angle = SkDoubleToScalar(fmod(timer.secs() * 360 / 24, 360)); 503 fAnimatingDrawable->setSweep(angle); 504 return true; 505 } 506 #endif 507 508 private: 509 sk_sp<DrawLitAtlasDrawable> fDrawable; 510 511 typedef SampleView INHERITED; 512 }; 513 514 ////////////////////////////////////////////////////////////////////////////// 515 516 static SkView* MyFactory() { return new DrawLitAtlasView; } 517 static SkViewRegister reg(MyFactory); 518