From adf5afa628adb62a0ad451d07ef1442381a0ee20 Mon Sep 17 00:00:00 2001 From: robertphillips Date: Fri, 3 Jun 2016 10:12:08 -0700 Subject: [PATCH] Add SampleApp slide with animating lightmapped objects & transparency This is pulled out of the drawLitAtlas CL (may it someday land). It does nicely demonstrate animating normal mapped objects and normal maps combined with partially transparent diffuse textures. It is a crude Asteroids game. GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2026393005 TBR=reed@google.com Review-Url: https://codereview.chromium.org/2026393005 --- gm/SkLinearBitmapPipelineGM.cpp | 1 - gyp/SampleApp.gyp | 1 + include/core/SkLights.h | 2 +- samplecode/SampleLitAtlas.cpp | 467 ++++++++++++++++++++++++++++++++ 4 files changed, 469 insertions(+), 2 deletions(-) create mode 100644 samplecode/SampleLitAtlas.cpp diff --git a/gm/SkLinearBitmapPipelineGM.cpp b/gm/SkLinearBitmapPipelineGM.cpp index 0a66b9c4df..777f825780 100644 --- a/gm/SkLinearBitmapPipelineGM.cpp +++ b/gm/SkLinearBitmapPipelineGM.cpp @@ -15,7 +15,6 @@ #include "SkXfermode.h" #include "SkPM4fPriv.h" #include "SkShader.h" -#include "SkBitmapProcShader.h" static void fill_in_bits(SkBitmap& bm, SkIRect ir, SkColor c, bool premul) { bm.allocN32Pixels(ir.width(), ir.height()); diff --git a/gyp/SampleApp.gyp b/gyp/SampleApp.gyp index 44e5904461..f074df3b2f 100644 --- a/gyp/SampleApp.gyp +++ b/gyp/SampleApp.gyp @@ -84,6 +84,7 @@ '../samplecode/SampleLCD.cpp', '../samplecode/SampleLighting.cpp', '../samplecode/SampleLines.cpp', + '../samplecode/SampleLitAtlas.cpp', '../samplecode/SampleLua.cpp', '../samplecode/SampleManyRects.cpp', '../samplecode/SampleMeasure.cpp', diff --git a/include/core/SkLights.h b/include/core/SkLights.h index 0a4fb8153e..c5c54276a3 100644 --- a/include/core/SkLights.h +++ b/include/core/SkLights.h @@ -61,7 +61,7 @@ public: } } - const sk_sp finish() { + sk_sp finish() { return fLights; } diff --git a/samplecode/SampleLitAtlas.cpp b/samplecode/SampleLitAtlas.cpp new file mode 100644 index 0000000000..4f989af1ae --- /dev/null +++ b/samplecode/SampleLitAtlas.cpp @@ -0,0 +1,467 @@ +/* + * 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 "SkView.h" +#include "SkCanvas.h" +#include "SkDrawable.h" +#include "SkLightingShader.h" +#include "SkLights.h" +#include "SkRandom.h" +#include "SkRSXform.h" + +#include "sk_tool_utils.h" + +class DrawLitAtlasDrawable : public SkDrawable { +public: + DrawLitAtlasDrawable(const SkRect& r) + : fBounds(r) + , fUseColors(false) { + 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]); + + SkLights::Builder builder; + + builder.add(SkLights::Light(SkColor3f::Make(1.0f, 1.0f, 1.0f), + SkVector3::Make(1.0f, 0.0f, 0.0f))); + builder.add(SkLights::Light(SkColor3f::Make(0.2f, 0.2f, 0.2f))); + + fLights = builder.finish(); + } + + void toggleUseColors() { + fUseColors = !fUseColors; + } + + 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 +#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]); + + // TODO: correctly pull out the pure rotation + SkVector invNormRotation = { m[SkMatrix::kMScaleX], m[SkMatrix::kMSkewY] }; + + paint.setShader(SkLightingShader::Make(fAtlas, fAtlas, fLights, + invNormRotation, &diffMat, &normalMat)); + + 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; + + // 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(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; + sk_sp 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; + 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 fDrawable; + + typedef SampleView INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +static SkView* MyFactory() { return new DrawLitAtlasView; } +static SkViewRegister reg(MyFactory); -- 2.34.1