/* * 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 "Sample.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 "sk_tool_utils.h" // A crude normal mapped asteroids-like sample 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; SkScalar len = newVel.length(); if (len > 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 = SkShader::MakeBitmapShader(fAtlas, SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, &normalMat); sk_sp<SkNormalSource> normalSource = SkNormalSource::MakeFromNormalMap( std::move(normalMap), m); sk_sp<SkShader> diffuseShader = SkShader::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::MakeDirectional( SkColor3f::Make(1.0f, 1.0f, 1.0f), fLightDir)); builder.setAmbientLightColor(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 Sample { public: DrawLitAtlasView() : fDrawable(new DrawLitAtlasDrawable(SkRect::MakeWH(640, 480))) {} protected: bool onQuery(Sample::Event* evt) override { if (Sample::TitleQ(*evt)) { Sample::TitleR(evt, "DrawLitAtlas"); return true; } SkUnichar uni; if (Sample::CharQ(*evt, &uni)) { switch (uni) { case 'C': fDrawable->toggleUseColors(); return true; case 'j': fDrawable->left(); return true; case 'k': fDrawable->thrust(); return true; case 'l': fDrawable->right(); return true; case 'o': fDrawable->rotateLight(); return true; default: break; } } return this->INHERITED::onQuery(evt); } void onDrawContent(SkCanvas* canvas) override { canvas->drawDrawable(fDrawable.get()); } bool onAnimate(const SkAnimTimer& timer) override { return true; } private: sk_sp<DrawLitAtlasDrawable> fDrawable; typedef Sample INHERITED; }; ////////////////////////////////////////////////////////////////////////////// DEF_SAMPLE( return new DrawLitAtlasView(); )