Home | History | Annotate | Download | only in samplecode
      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