| /* | |
| * Copyright 2016 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "SampleCode.h" | |
| #include "SkAnimTimer.h" | |
| #include "SkBitmapProcShader.h" | |
| #include "SkCanvas.h" | |
| #include "SkDrawable.h" | |
| #include "SkLightingShader.h" | |
| #include "SkLights.h" | |
| #include "SkNormalSource.h" | |
| #include "SkRandom.h" | |
| #include "SkRSXform.h" | |
| #include "SkView.h" | |
| #include "sk_tool_utils.h" | |
| class DrawLitAtlasDrawable : public SkDrawable { | |
| public: | |
| DrawLitAtlasDrawable(const SkRect& r) | |
| : fBounds(r) | |
| , fUseColors(false) | |
| , fLightDir(SkVector3::Make(1.0f, 0.0f, 0.0f)) { | |
| fAtlas = MakeAtlas(); | |
| SkRandom rand; | |
| for (int i = 0; i < kNumAsteroids; ++i) { | |
| fAsteroids[i].initAsteroid(&rand, fBounds, &fDiffTex[i], &fNormTex[i]); | |
| } | |
| fShip.initShip(fBounds, &fDiffTex[kNumAsteroids], &fNormTex[kNumAsteroids]); | |
| this->updateLights(); | |
| } | |
| void toggleUseColors() { | |
| fUseColors = !fUseColors; | |
| } | |
| void rotateLight() { | |
| SkScalar c; | |
| SkScalar s = SkScalarSinCos(SK_ScalarPI/6.0f, &c); | |
| SkScalar newX = c * fLightDir.fX - s * fLightDir.fY; | |
| SkScalar newY = s * fLightDir.fX + c * fLightDir.fY; | |
| fLightDir.set(newX, newY, 0.0f); | |
| this->updateLights(); | |
| } | |
| void left() { | |
| SkScalar newRot = SkScalarMod(fShip.rot() + (2*SK_ScalarPI - SK_ScalarPI/32.0f), | |
| 2 * SK_ScalarPI); | |
| fShip.setRot(newRot); | |
| } | |
| void right() { | |
| SkScalar newRot = SkScalarMod(fShip.rot() + SK_ScalarPI/32.0f, 2 * SK_ScalarPI); | |
| fShip.setRot(newRot); | |
| } | |
| void thrust() { | |
| SkScalar c; | |
| SkScalar s = SkScalarSinCos(fShip.rot(), &c); | |
| SkVector newVel = fShip.velocity(); | |
| newVel.fX += s; | |
| newVel.fY += -c; | |
| if (newVel.lengthSqd() > kMaxShipSpeed*kMaxShipSpeed) { | |
| newVel.setLength(SkIntToScalar(kMaxShipSpeed)); | |
| } | |
| fShip.setVelocity(newVel); | |
| } | |
| protected: | |
| void onDraw(SkCanvas* canvas) override { | |
| SkRSXform xforms[kNumAsteroids+kNumShips]; | |
| SkColor colors[kNumAsteroids+kNumShips]; | |
| for (int i = 0; i < kNumAsteroids; ++i) { | |
| fAsteroids[i].advance(fBounds); | |
| xforms[i] = fAsteroids[i].asRSXform(); | |
| if (fUseColors) { | |
| colors[i] = SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF); | |
| } | |
| } | |
| fShip.advance(fBounds); | |
| xforms[kNumAsteroids] = fShip.asRSXform(); | |
| if (fUseColors) { | |
| colors[kNumAsteroids] = SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF); | |
| } | |
| #ifdef SK_DEBUG | |
| canvas->drawBitmap(fAtlas, 0, 0); // just to see the atlas | |
| this->drawLightDir(canvas, fBounds.centerX(), fBounds.centerY()); | |
| #endif | |
| #if 0 | |
| // TODO: revitalize when drawLitAtlas API lands | |
| SkPaint paint; | |
| paint.setFilterQuality(kLow_SkFilterQuality); | |
| const SkRect cull = this->getBounds(); | |
| const SkColor* colorsPtr = fUseColors ? colors : NULL; | |
| canvas->drawLitAtlas(fAtlas, xforms, fDiffTex, fNormTex, colorsPtr, kNumAsteroids+1, | |
| SkXfermode::kModulate_Mode, &cull, &paint, fLights); | |
| #else | |
| SkMatrix diffMat, normalMat; | |
| for (int i = 0; i < kNumAsteroids+1; ++i) { | |
| colors[i] = colors[i] & 0xFF000000; // to silence compilers | |
| SkPaint paint; | |
| SkRect r = fDiffTex[i]; | |
| r.offsetTo(0, 0); | |
| diffMat.setRectToRect(fDiffTex[i], r, SkMatrix::kFill_ScaleToFit); | |
| normalMat.setRectToRect(fNormTex[i], r, SkMatrix::kFill_ScaleToFit); | |
| SkMatrix m; | |
| m.setRSXform(xforms[i]); | |
| sk_sp<SkShader> normalMap = SkMakeBitmapShader(fAtlas, SkShader::kClamp_TileMode, | |
| SkShader::kClamp_TileMode, &normalMat, nullptr); | |
| sk_sp<SkNormalSource> normalSource = SkNormalSource::MakeFromNormalMap( | |
| std::move(normalMap), m); | |
| sk_sp<SkShader> diffuseShader = SkBitmapProcShader::MakeBitmapShader(fAtlas, | |
| SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, &diffMat); | |
| paint.setShader(SkLightingShader::Make(std::move(diffuseShader), | |
| std::move(normalSource), fLights)); | |
| canvas->save(); | |
| canvas->setMatrix(m); | |
| canvas->drawRect(r, paint); | |
| canvas->restore(); | |
| } | |
| #endif | |
| #ifdef SK_DEBUG | |
| { | |
| SkPaint paint; | |
| paint.setColor(SK_ColorRED); | |
| for (int i = 0; i < kNumAsteroids; ++i) { | |
| canvas->drawCircle(fAsteroids[i].pos().x(), fAsteroids[i].pos().y(), 2, paint); | |
| } | |
| canvas->drawCircle(fShip.pos().x(), fShip.pos().y(), 2, paint); | |
| paint.setStyle(SkPaint::kStroke_Style); | |
| canvas->drawRect(this->getBounds(), paint); | |
| } | |
| #endif | |
| } | |
| SkRect onGetBounds() override { | |
| return fBounds; | |
| } | |
| private: | |
| enum ObjType { | |
| kBigAsteroid_ObjType = 0, | |
| kMedAsteroid_ObjType, | |
| kSmAsteroid_ObjType, | |
| kShip_ObjType, | |
| kLast_ObjType = kShip_ObjType | |
| }; | |
| static const int kObjTypeCount = kLast_ObjType + 1; | |
| void updateLights() { | |
| SkLights::Builder builder; | |
| builder.add(SkLights::Light(SkColor3f::Make(1.0f, 1.0f, 1.0f), fLightDir)); | |
| builder.add(SkLights::Light(SkColor3f::Make(0.2f, 0.2f, 0.2f))); | |
| fLights = builder.finish(); | |
| } | |
| #ifdef SK_DEBUG | |
| // Draw a vector to the light | |
| void drawLightDir(SkCanvas* canvas, SkScalar centerX, SkScalar centerY) { | |
| static const int kBgLen = 30; | |
| static const int kSmLen = 5; | |
| // TODO: change the lighting coordinate system to be right handed | |
| SkPoint p1 = SkPoint::Make(centerX + kBgLen * fLightDir.fX, | |
| centerY - kBgLen * fLightDir.fY); | |
| SkPoint p2 = SkPoint::Make(centerX + (kBgLen-kSmLen) * fLightDir.fX, | |
| centerY - (kBgLen-kSmLen) * fLightDir.fY); | |
| SkPaint p; | |
| canvas->drawLine(centerX, centerY, p1.fX, p1.fY, p); | |
| canvas->drawLine(p1.fX, p1.fY, | |
| p2.fX - kSmLen * fLightDir.fY, p2.fY - kSmLen * fLightDir.fX, p); | |
| canvas->drawLine(p1.fX, p1.fY, | |
| p2.fX + kSmLen * fLightDir.fY, p2.fY + kSmLen * fLightDir.fX, p); | |
| } | |
| #endif | |
| // Create the mixed diffuse & normal atlas | |
| // | |
| // big color circle | big normal hemi | |
| // ------------------------------------ | |
| // med color circle | med normal pyra | |
| // ------------------------------------ | |
| // sm color circle | sm normal hemi | |
| // ------------------------------------ | |
| // big ship | big tetra normal | |
| static SkBitmap MakeAtlas() { | |
| SkBitmap atlas; | |
| atlas.allocN32Pixels(kAtlasWidth, kAtlasHeight); | |
| for (int y = 0; y < kAtlasHeight; ++y) { | |
| int x = 0; | |
| for ( ; x < kBigSize+kPad; ++x) { | |
| *atlas.getAddr32(x, y) = SK_ColorTRANSPARENT; | |
| } | |
| for ( ; x < kAtlasWidth; ++x) { | |
| *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0x88, 0x88, 0xFF); | |
| } | |
| } | |
| // big asteroid | |
| { | |
| SkPoint bigCenter = SkPoint::Make(kDiffXOff + kBigSize/2.0f, kBigYOff + kBigSize/2.0f); | |
| for (int y = kBigYOff; y < kBigYOff+kBigSize; ++y) { | |
| for (int x = kDiffXOff; x < kDiffXOff+kBigSize; ++x) { | |
| SkScalar distSq = (x - bigCenter.fX) * (x - bigCenter.fX) + | |
| (y - bigCenter.fY) * (y - bigCenter.fY); | |
| if (distSq > kBigSize*kBigSize/4.0f) { | |
| *atlas.getAddr32(x, y) = SkPreMultiplyARGB(0, 0, 0, 0); | |
| } else { | |
| *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0xFF, 0, 0); | |
| } | |
| } | |
| } | |
| sk_tool_utils::create_hemi_normal_map(&atlas, | |
| SkIRect::MakeXYWH(kNormXOff, kBigYOff, | |
| kBigSize, kBigSize)); | |
| } | |
| // medium asteroid | |
| { | |
| for (int y = kMedYOff; y < kMedYOff+kMedSize; ++y) { | |
| for (int x = kDiffXOff; x < kDiffXOff+kMedSize; ++x) { | |
| *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0xFF, 0); | |
| } | |
| } | |
| sk_tool_utils::create_frustum_normal_map(&atlas, | |
| SkIRect::MakeXYWH(kNormXOff, kMedYOff, | |
| kMedSize, kMedSize)); | |
| } | |
| // small asteroid | |
| { | |
| SkPoint smCenter = SkPoint::Make(kDiffXOff + kSmSize/2.0f, kSmYOff + kSmSize/2.0f); | |
| for (int y = kSmYOff; y < kSmYOff+kSmSize; ++y) { | |
| for (int x = kDiffXOff; x < kDiffXOff+kSmSize; ++x) { | |
| SkScalar distSq = (x - smCenter.fX) * (x - smCenter.fX) + | |
| (y - smCenter.fY) * (y - smCenter.fY); | |
| if (distSq > kSmSize*kSmSize/4.0f) { | |
| *atlas.getAddr32(x, y) = SkPreMultiplyARGB(0, 0, 0, 0); | |
| } else { | |
| *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0, 0xFF); | |
| } | |
| } | |
| } | |
| sk_tool_utils::create_hemi_normal_map(&atlas, | |
| SkIRect::MakeXYWH(kNormXOff, kSmYOff, | |
| kSmSize, kSmSize)); | |
| } | |
| // ship | |
| { | |
| SkScalar shipMidLine = kDiffXOff + kMedSize/2.0f; | |
| for (int y = kShipYOff; y < kShipYOff+kMedSize; ++y) { | |
| SkScalar scaledY = (y - kShipYOff)/(float)kMedSize; // 0..1 | |
| for (int x = kDiffXOff; x < kDiffXOff+kMedSize; ++x) { | |
| SkScalar scaledX; | |
| if (x < shipMidLine) { | |
| scaledX = 1.0f - (x - kDiffXOff)/(kMedSize/2.0f); // 0..1 | |
| } else { | |
| scaledX = (x - shipMidLine)/(kMedSize/2.0f); // 0..1 | |
| } | |
| if (scaledX < scaledY) { | |
| *atlas.getAddr32(x, y) = SkPackARGB32(0xFF, 0, 0xFF, 0xFF); | |
| } else { | |
| *atlas.getAddr32(x, y) = SkPackARGB32(0, 0, 0, 0); | |
| } | |
| } | |
| } | |
| sk_tool_utils::create_tetra_normal_map(&atlas, | |
| SkIRect::MakeXYWH(kNormXOff, kShipYOff, | |
| kMedSize, kMedSize)); | |
| } | |
| return atlas; | |
| } | |
| class ObjectRecord { | |
| public: | |
| void initAsteroid(SkRandom *rand, const SkRect& bounds, | |
| SkRect* diffTex, SkRect* normTex) { | |
| static const SkScalar gMaxSpeeds[3] = { 1, 2, 5 }; // smaller asteroids can go faster | |
| static const SkScalar gYOffs[3] = { kBigYOff, kMedYOff, kSmYOff }; | |
| static const SkScalar gSizes[3] = { kBigSize, kMedSize, kSmSize }; | |
| static unsigned int asteroidType = 0; | |
| fObjType = static_cast<ObjType>(asteroidType++ % 3); | |
| fPosition.set(bounds.fLeft + rand->nextUScalar1() * bounds.width(), | |
| bounds.fTop + rand->nextUScalar1() * bounds.height()); | |
| fVelocity.fX = rand->nextSScalar1(); | |
| fVelocity.fY = sqrt(1.0f - fVelocity.fX * fVelocity.fX); | |
| SkASSERT(SkScalarNearlyEqual(fVelocity.length(), 1.0f)); | |
| fVelocity *= gMaxSpeeds[fObjType]; | |
| fRot = 0; | |
| fDeltaRot = rand->nextSScalar1() / 32; | |
| diffTex->setXYWH(SkIntToScalar(kDiffXOff), gYOffs[fObjType], | |
| gSizes[fObjType], gSizes[fObjType]); | |
| normTex->setXYWH(SkIntToScalar(kNormXOff), gYOffs[fObjType], | |
| gSizes[fObjType], gSizes[fObjType]); | |
| } | |
| void initShip(const SkRect& bounds, SkRect* diffTex, SkRect* normTex) { | |
| fObjType = kShip_ObjType; | |
| fPosition.set(bounds.centerX(), bounds.centerY()); | |
| fVelocity = SkVector::Make(0.0f, 0.0f); | |
| fRot = 0.0f; | |
| fDeltaRot = 0.0f; | |
| diffTex->setXYWH(SkIntToScalar(kDiffXOff), SkIntToScalar(kShipYOff), | |
| SkIntToScalar(kMedSize), SkIntToScalar(kMedSize)); | |
| normTex->setXYWH(SkIntToScalar(kNormXOff), SkIntToScalar(kShipYOff), | |
| SkIntToScalar(kMedSize), SkIntToScalar(kMedSize)); | |
| } | |
| void advance(const SkRect& bounds) { | |
| fPosition += fVelocity; | |
| if (fPosition.fX > bounds.right()) { | |
| SkASSERT(fVelocity.fX > 0); | |
| fVelocity.fX = -fVelocity.fX; | |
| } else if (fPosition.fX < bounds.left()) { | |
| SkASSERT(fVelocity.fX < 0); | |
| fVelocity.fX = -fVelocity.fX; | |
| } | |
| if (fPosition.fY > bounds.bottom()) { | |
| if (fVelocity.fY > 0) { | |
| fVelocity.fY = -fVelocity.fY; | |
| } | |
| } else if (fPosition.fY < bounds.top()) { | |
| if (fVelocity.fY < 0) { | |
| fVelocity.fY = -fVelocity.fY; | |
| } | |
| } | |
| fRot += fDeltaRot; | |
| fRot = SkScalarMod(fRot, 2 * SK_ScalarPI); | |
| } | |
| const SkPoint& pos() const { return fPosition; } | |
| SkScalar rot() const { return fRot; } | |
| void setRot(SkScalar rot) { fRot = rot; } | |
| const SkPoint& velocity() const { return fVelocity; } | |
| void setVelocity(const SkPoint& velocity) { fVelocity = velocity; } | |
| SkRSXform asRSXform() const { | |
| static const SkScalar gHalfSizes[kObjTypeCount] = { | |
| SkScalarHalf(kBigSize), | |
| SkScalarHalf(kMedSize), | |
| SkScalarHalf(kSmSize), | |
| SkScalarHalf(kMedSize), | |
| }; | |
| return SkRSXform::MakeFromRadians(1.0f, fRot, fPosition.x(), fPosition.y(), | |
| gHalfSizes[fObjType], | |
| gHalfSizes[fObjType]); | |
| } | |
| private: | |
| ObjType fObjType; | |
| SkPoint fPosition; | |
| SkVector fVelocity; | |
| SkScalar fRot; // In radians. | |
| SkScalar fDeltaRot; // In radiands. Not used by ship. | |
| }; | |
| private: | |
| static const int kNumLights = 2; | |
| static const int kNumAsteroids = 6; | |
| static const int kNumShips = 1; | |
| static const int kBigSize = 128; | |
| static const int kMedSize = 64; | |
| static const int kSmSize = 32; | |
| static const int kPad = 1; | |
| static const int kAtlasWidth = kBigSize + kBigSize + 2 * kPad; // 2 pads in the middle | |
| static const int kAtlasHeight = kBigSize + kMedSize + kSmSize + kMedSize + 3 * kPad; | |
| static const int kDiffXOff = 0; | |
| static const int kNormXOff = kBigSize + 2 * kPad; | |
| static const int kBigYOff = 0; | |
| static const int kMedYOff = kBigSize + kPad; | |
| static const int kSmYOff = kMedYOff + kMedSize + kPad; | |
| static const int kShipYOff = kSmYOff + kSmSize + kPad; | |
| static const int kMaxShipSpeed = 5; | |
| SkBitmap fAtlas; | |
| ObjectRecord fAsteroids[kNumAsteroids]; | |
| ObjectRecord fShip; | |
| SkRect fDiffTex[kNumAsteroids+kNumShips]; | |
| SkRect fNormTex[kNumAsteroids+kNumShips]; | |
| SkRect fBounds; | |
| bool fUseColors; | |
| SkVector3 fLightDir; | |
| sk_sp<SkLights> fLights; | |
| typedef SkDrawable INHERITED; | |
| }; | |
| class DrawLitAtlasView : public SampleView { | |
| public: | |
| DrawLitAtlasView() | |
| : fDrawable(new DrawLitAtlasDrawable(SkRect::MakeWH(640, 480))) { | |
| } | |
| protected: | |
| bool onQuery(SkEvent* evt) override { | |
| if (SampleCode::TitleQ(*evt)) { | |
| SampleCode::TitleR(evt, "DrawLitAtlas"); | |
| return true; | |
| } | |
| SkUnichar uni; | |
| if (SampleCode::CharQ(*evt, &uni)) { | |
| switch (uni) { | |
| case 'C': | |
| fDrawable->toggleUseColors(); | |
| this->inval(NULL); | |
| return true; | |
| case 'j': | |
| fDrawable->left(); | |
| this->inval(NULL); | |
| return true; | |
| case 'k': | |
| fDrawable->thrust(); | |
| this->inval(NULL); | |
| return true; | |
| case 'l': | |
| fDrawable->right(); | |
| this->inval(NULL); | |
| return true; | |
| case 'o': | |
| fDrawable->rotateLight(); | |
| this->inval(NULL); | |
| return true; | |
| default: | |
| break; | |
| } | |
| } | |
| return this->INHERITED::onQuery(evt); | |
| } | |
| void onDrawContent(SkCanvas* canvas) override { | |
| canvas->drawDrawable(fDrawable); | |
| this->inval(NULL); | |
| } | |
| #if 0 | |
| // TODO: switch over to use this for our animation | |
| bool onAnimate(const SkAnimTimer& timer) override { | |
| SkScalar angle = SkDoubleToScalar(fmod(timer.secs() * 360 / 24, 360)); | |
| fAnimatingDrawable->setSweep(angle); | |
| return true; | |
| } | |
| #endif | |
| private: | |
| SkAutoTUnref<DrawLitAtlasDrawable> fDrawable; | |
| typedef SampleView INHERITED; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| static SkView* MyFactory() { return new DrawLitAtlasView; } | |
| static SkViewRegister reg(MyFactory); |