skia2/samplecode/SampleSVGPong.cpp
Florin Malita d75f3f99d4 Fix SVGPong slide crash in Viewer
SVGPong relies on onOnceBeforeDraw() to initialize, but when switching
slides via Viewer's UI, onAnimate() is fired before the first paint.

Change-Id: I7be133072ec635975f7463538cd42edb69696b3f
Reviewed-on: https://skia-review.googlesource.com/79424
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
2017-12-03 01:15:32 +00:00

290 lines
10 KiB
C++

/*
* 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 "SkColor.h"
#include "SkRandom.h"
#include "SkRRect.h"
#include "SkSVGDOM.h"
#include "SkSVGG.h"
#include "SkSVGPath.h"
#include "SkSVGRect.h"
#include "SkSVGSVG.h"
namespace {
static const SkRect kBounds = SkRect::MakeLTRB(0.1f, 0.1f, 0.9f, 0.9f);
static const SkSize kPaddleSize = SkSize::Make(0.03f, 0.1f);
static const SkScalar kBallSize = 0.04f;
static const SkScalar kShadowOpacity = 0.40f;
static const SkScalar kShadowParallax = 0.04f;
static const SkScalar kBackgroundStroke = 0.01f;
static const uint32_t kBackgroundDashCount = 20;
static const SkScalar kBallSpeedMax = 0.0020f;
static const SkScalar kBallSpeedMin = 0.0005f;
static const SkScalar kBallSpeedFuzz = 0.0002f;
static const SkScalar kTimeScaleMin = 0.0f;
static const SkScalar kTimeScaleMax = 5.0f;
// Box the value within [min, max), by applying infinite reflection on the interval endpoints.
SkScalar box_reflect(SkScalar v, SkScalar min, SkScalar max) {
const SkScalar intervalLen = max - min;
SkASSERT(intervalLen > 0);
// f(v) is periodic in 2 * intervalLen: one normal progression + one reflection
const SkScalar P = intervalLen * 2;
// relative to P origin
const SkScalar vP = v - min;
// map to [0, P)
const SkScalar vMod = (vP < 0) ? P - SkScalarMod(-vP, P) : SkScalarMod(vP, P);
// reflect if needed, to map to [0, intervalLen)
const SkScalar vInterval = vMod < intervalLen ? vMod : P - vMod;
// finally, reposition relative to min
return vInterval + min;
}
// Compute <t, y> for the trajectory intersection with the next vertical edge.
std::tuple<SkScalar, SkScalar> find_yintercept(const SkPoint& pos, const SkVector& spd,
const SkRect& box) {
const SkScalar edge = spd.fX > 0 ? box.fRight : box.fLeft;
const SkScalar t = (edge - pos.fX) / spd.fX;
SkASSERT(t >= 0);
const SkScalar dY = t * spd.fY;
return std::make_tuple(t, box_reflect(pos.fY + dY, box.fTop, box.fBottom));
}
sk_sp<SkSVGRect> make_svg_rrect(const SkRRect& rrect) {
sk_sp<SkSVGRect> node = SkSVGRect::Make();
node->setX(SkSVGLength(rrect.rect().x()));
node->setY(SkSVGLength(rrect.rect().y()));
node->setWidth(SkSVGLength(rrect.width()));
node->setHeight(SkSVGLength(rrect.height()));
node->setRx(SkSVGLength(rrect.getSimpleRadii().x()));
node->setRy(SkSVGLength(rrect.getSimpleRadii().y()));
return node;
}
} // anonymous ns
class SVGPongView final : public SampleView {
public:
SVGPongView() {}
protected:
void onOnceBeforeDraw() override {
const SkRect fieldBounds = kBounds.makeOutset(kBallSize / 2, kBallSize / 2);
const SkRRect ball = SkRRect::MakeOval(SkRect::MakeWH(kBallSize, kBallSize));
const SkRRect paddle = SkRRect::MakeRectXY(SkRect::MakeWH(kPaddleSize.width(),
kPaddleSize.height()),
kPaddleSize.width() / 2,
kPaddleSize.width() / 2);
fBall.initialize(ball,
SK_ColorGREEN,
SkPoint::Make(kBounds.centerX(), kBounds.centerY()),
SkVector::Make(fRand.nextRangeScalar(kBallSpeedMin, kBallSpeedMax),
fRand.nextRangeScalar(kBallSpeedMin, kBallSpeedMax)));
fPaddle0.initialize(paddle,
SK_ColorBLUE,
SkPoint::Make(fieldBounds.left() - kPaddleSize.width() / 2,
fieldBounds.centerY()),
SkVector::Make(0, 0));
fPaddle1.initialize(paddle,
SK_ColorRED,
SkPoint::Make(fieldBounds.right() + kPaddleSize.width() / 2,
fieldBounds.centerY()),
SkVector::Make(0, 0));
// Background decoration.
SkPath bgPath;
bgPath.moveTo(kBounds.left() , fieldBounds.top());
bgPath.lineTo(kBounds.right(), fieldBounds.top());
bgPath.moveTo(kBounds.left() , fieldBounds.bottom());
bgPath.lineTo(kBounds.right(), fieldBounds.bottom());
// TODO: stroke-dash support would come in handy right about now.
for (uint32_t i = 0; i < kBackgroundDashCount; ++i) {
bgPath.moveTo(kBounds.centerX(),
kBounds.top() + (i + 0.25f) * kBounds.height() / kBackgroundDashCount);
bgPath.lineTo(kBounds.centerX(),
kBounds.top() + (i + 0.75f) * kBounds.height() / kBackgroundDashCount);
}
sk_sp<SkSVGPath> bg = SkSVGPath::Make();
bg->setPath(bgPath);
bg->setFill(SkSVGPaint(SkSVGPaint::Type::kNone));
bg->setStroke(SkSVGPaint(SkSVGColorType(SK_ColorBLACK)));
bg->setStrokeWidth(SkSVGLength(kBackgroundStroke));
// Build the SVG DOM tree.
sk_sp<SkSVGSVG> root = SkSVGSVG::Make();
root->appendChild(std::move(bg));
root->appendChild(fPaddle0.shadowNode);
root->appendChild(fPaddle1.shadowNode);
root->appendChild(fBall.shadowNode);
root->appendChild(fPaddle0.objectNode);
root->appendChild(fPaddle1.objectNode);
root->appendChild(fBall.objectNode);
// Handle everything in a normalized 1x1 space.
root->setViewBox(SkSVGViewBoxType(SkRect::MakeWH(1, 1)));
fDom = sk_sp<SkSVGDOM>(new SkSVGDOM());
fDom->setContainerSize(SkSize::Make(this->width(), this->height()));
fDom->setRoot(std::move(root));
// Off we go.
this->updatePaddleStrategy();
}
bool onQuery(SkEvent* evt) override {
if (SampleCode::TitleQ(*evt)) {
SampleCode::TitleR(evt, "SVGPong");
return true;
}
SkUnichar uni;
if (SampleCode::CharQ(*evt, &uni)) {
switch (uni) {
case '[':
fTimeScale = SkTPin(fTimeScale - 0.1f, kTimeScaleMin, kTimeScaleMax);
return true;
case ']':
fTimeScale = SkTPin(fTimeScale + 0.1f, kTimeScaleMin, kTimeScaleMax);
return true;
default:
break;
}
}
return this->INHERITED::onQuery(evt);
}
void onSizeChange() override {
if (fDom) {
fDom->setContainerSize(SkSize::Make(this->width(), this->height()));
}
this->INHERITED::onSizeChange();
}
void onDrawContent(SkCanvas* canvas) override {
fDom->render(canvas);
}
bool onAnimate(const SkAnimTimer& timer) override {
// onAnimate may fire before the first draw.
if (fDom) {
SkScalar dt = (timer.msec() - fLastTick) * fTimeScale;
fLastTick = timer.msec();
fPaddle0.posTick(dt);
fPaddle1.posTick(dt);
fBall.posTick(dt);
this->enforceConstraints();
fPaddle0.updateDom();
fPaddle1.updateDom();
fBall.updateDom();
}
return true;
}
private:
struct Object {
void initialize(const SkRRect& rrect, SkColor color,
const SkPoint& p, const SkVector& s) {
objectNode = make_svg_rrect(rrect);
objectNode->setFill(SkSVGPaint(SkSVGColorType(color)));
shadowNode = make_svg_rrect(rrect);
shadowNode->setFillOpacity(SkSVGNumberType(kShadowOpacity));
pos = p;
spd = s;
size = SkSize::Make(rrect.width(), rrect.height());
}
void posTick(SkScalar dt) {
pos += spd * dt;
}
void updateDom() {
const SkPoint corner = pos - SkPoint::Make(size.width() / 2, size.height() / 2);
objectNode->setX(SkSVGLength(corner.x()));
objectNode->setY(SkSVGLength(corner.y()));
// Simulate parallax shadow for a centered light source.
SkPoint shadowOffset = pos - SkPoint::Make(kBounds.centerX(), kBounds.centerY());
shadowOffset.scale(kShadowParallax);
const SkPoint shadowCorner = corner + shadowOffset;
shadowNode->setX(SkSVGLength(shadowCorner.x()));
shadowNode->setY(SkSVGLength(shadowCorner.y()));
}
sk_sp<SkSVGRect> objectNode;
sk_sp<SkSVGRect> shadowNode;
SkPoint pos;
SkVector spd;
SkSize size;
};
void enforceConstraints() {
// Perfect vertical reflection.
if (fBall.pos.fY < kBounds.fTop || fBall.pos.fY >= kBounds.fBottom) {
fBall.spd.fY = -fBall.spd.fY;
fBall.pos.fY = box_reflect(fBall.pos.fY, kBounds.fTop, kBounds.fBottom);
}
// Horizontal bounce - introduces a speed fuzz.
if (fBall.pos.fX < kBounds.fLeft || fBall.pos.fX >= kBounds.fRight) {
fBall.spd.fX = this->fuzzBallSpeed(-fBall.spd.fX);
fBall.spd.fY = this->fuzzBallSpeed(fBall.spd.fY);
fBall.pos.fX = box_reflect(fBall.pos.fX, kBounds.fLeft, kBounds.fRight);
this->updatePaddleStrategy();
}
}
SkScalar fuzzBallSpeed(SkScalar spd) {
// The speed limits are absolute values.
const SkScalar sign = spd >= 0 ? 1.0f : -1.0f;
const SkScalar fuzzed = fabs(spd) + fRand.nextRangeScalar(-kBallSpeedFuzz, kBallSpeedFuzz);
return sign * SkTPin(fuzzed, kBallSpeedMin, kBallSpeedMax);
}
void updatePaddleStrategy() {
Object* pitcher = fBall.spd.fX > 0 ? &fPaddle0 : &fPaddle1;
Object* catcher = fBall.spd.fX > 0 ? &fPaddle1 : &fPaddle0;
SkScalar t, yIntercept;
std::tie(t, yIntercept) = find_yintercept(fBall.pos, fBall.spd, kBounds);
// The pitcher aims for a neutral/centered position.
pitcher->spd.fY = (kBounds.centerY() - pitcher->pos.fY) / t;
// The catcher goes for the ball. Duh.
catcher->spd.fY = (yIntercept - catcher->pos.fY) / t;
}
sk_sp<SkSVGDOM> fDom;
Object fPaddle0, fPaddle1, fBall;
SkRandom fRand;
SkMSec fLastTick = 0;
SkScalar fTimeScale = 1.0f;
typedef SampleView INHERITED;
};
static SkView* SVGPongFactory() { return new SVGPongView; }
static SkViewRegister reg(SVGPongFactory);