| Index: samplecode/SampleLitAtlas.cpp
 | 
| diff --git a/samplecode/SampleLitAtlas.cpp b/samplecode/SampleLitAtlas.cpp
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..4f989af1ae026f5fdb7a9511bcf0d24a2cc3668d
 | 
| --- /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<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;
 | 
| +    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;
 | 
| +                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);
 | 
| 
 |