diff options
author | fmalita <fmalita@chromium.org> | 2016-08-12 13:17:11 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-08-12 13:17:11 -0700 |
commit | ca39d716f10e75e3a2449262e67584f46d589c26 (patch) | |
tree | b34b30321c6a139b7383b1a226690acd64fbecbb | |
parent | 09e12a6d4a75d281c47e00a64eeba893ba096f9e (diff) |
[SVGDom] SVGPong sample app
Shows off SVG dom-based animations.
R=robertphillips@google.com,stephana@google.com
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2243853003
Review-Url: https://codereview.chromium.org/2243853003
-rw-r--r-- | experimental/svg/model/SkSVGDOM.cpp | 4 | ||||
-rw-r--r-- | experimental/svg/model/SkSVGDOM.h | 1 | ||||
-rw-r--r-- | experimental/svg/model/SkSVGNode.cpp | 34 | ||||
-rw-r--r-- | experimental/svg/model/SkSVGNode.h | 6 | ||||
-rw-r--r-- | gyp/samples.gypi | 1 | ||||
-rw-r--r-- | samplecode/SampleSVGPong.cpp | 286 |
6 files changed, 325 insertions, 7 deletions
diff --git a/experimental/svg/model/SkSVGDOM.cpp b/experimental/svg/model/SkSVGDOM.cpp index 4ae5fedd3e..c9d9e93f65 100644 --- a/experimental/svg/model/SkSVGDOM.cpp +++ b/experimental/svg/model/SkSVGDOM.cpp @@ -355,3 +355,7 @@ void SkSVGDOM::setContainerSize(const SkSize& containerSize) { // TODO: inval fContainerSize = containerSize; } + +void SkSVGDOM::setRoot(sk_sp<SkSVGNode> root) { + fRoot = std::move(root); +} diff --git a/experimental/svg/model/SkSVGDOM.h b/experimental/svg/model/SkSVGDOM.h index 468ac00a90..98f846246c 100644 --- a/experimental/svg/model/SkSVGDOM.h +++ b/experimental/svg/model/SkSVGDOM.h @@ -26,6 +26,7 @@ public: static sk_sp<SkSVGDOM> MakeFromStream(SkStream&, const SkSize& containerSize); void setContainerSize(const SkSize&); + void setRoot(sk_sp<SkSVGNode>); void render(SkCanvas*) const; diff --git a/experimental/svg/model/SkSVGNode.cpp b/experimental/svg/model/SkSVGNode.cpp index d60c984cea..a039b59ff1 100644 --- a/experimental/svg/model/SkSVGNode.cpp +++ b/experimental/svg/model/SkSVGNode.cpp @@ -33,28 +33,48 @@ void SkSVGNode::setAttribute(SkSVGAttribute attr, const SkSVGValue& v) { this->onSetAttribute(attr, v); } +void SkSVGNode::setFill(const SkSVGPaint& svgPaint) { + fPresentationAttributes.fFill.set(svgPaint); +} + +void SkSVGNode::setFillOpacity(const SkSVGNumberType& opacity) { + fPresentationAttributes.fFillOpacity.set( + SkSVGNumberType(SkTPin<SkScalar>(opacity.value(), 0, 1))); +} + +void SkSVGNode::setStroke(const SkSVGPaint& svgPaint) { + fPresentationAttributes.fStroke.set(svgPaint); +} + +void SkSVGNode::setStrokeOpacity(const SkSVGNumberType& opacity) { + fPresentationAttributes.fStrokeOpacity.set( + SkSVGNumberType(SkTPin<SkScalar>(opacity.value(), 0, 1))); +} + +void SkSVGNode::setStrokeWidth(const SkSVGLength& strokeWidth) { + fPresentationAttributes.fStrokeWidth.set(strokeWidth); +} + void SkSVGNode::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) { switch (attr) { case SkSVGAttribute::kFill: if (const SkSVGPaintValue* paint = v.as<SkSVGPaintValue>()) { - fPresentationAttributes.fFill.set(*paint); + this->setFill(*paint); } break; case SkSVGAttribute::kFillOpacity: if (const SkSVGNumberValue* opacity = v.as<SkSVGNumberValue>()) { - fPresentationAttributes.fFillOpacity.set( - SkSVGNumberType(SkTPin<SkScalar>((*opacity)->value(), 0, 1))); + this->setFillOpacity(*opacity); } break; case SkSVGAttribute::kStroke: if (const SkSVGPaintValue* paint = v.as<SkSVGPaintValue>()) { - fPresentationAttributes.fStroke.set(*paint); + this->setStroke(*paint); } break; case SkSVGAttribute::kStrokeOpacity: if (const SkSVGNumberValue* opacity = v.as<SkSVGNumberValue>()) { - fPresentationAttributes.fStrokeOpacity.set( - SkSVGNumberType(SkTPin<SkScalar>((*opacity)->value(), 0, 1))); + this->setStrokeOpacity(*opacity); } break; case SkSVGAttribute::kStrokeLineCap: @@ -69,7 +89,7 @@ void SkSVGNode::onSetAttribute(SkSVGAttribute attr, const SkSVGValue& v) { break; case SkSVGAttribute::kStrokeWidth: if (const SkSVGLengthValue* strokeWidth = v.as<SkSVGLengthValue>()) { - fPresentationAttributes.fStrokeWidth.set(*strokeWidth); + this->setStrokeWidth(*strokeWidth); } break; default: diff --git a/experimental/svg/model/SkSVGNode.h b/experimental/svg/model/SkSVGNode.h index 1e5c3be50f..8ffc6f9769 100644 --- a/experimental/svg/model/SkSVGNode.h +++ b/experimental/svg/model/SkSVGNode.h @@ -37,6 +37,12 @@ public: void setAttribute(SkSVGAttribute, const SkSVGValue&); + void setFill(const SkSVGPaint&); + void setFillOpacity(const SkSVGNumberType&); + void setStroke(const SkSVGPaint&); + void setStrokeOpacity(const SkSVGNumberType&); + void setStrokeWidth(const SkSVGLength&); + protected: SkSVGNode(SkSVGTag); diff --git a/gyp/samples.gypi b/gyp/samples.gypi index 2a33b355ea..d1f1f607a2 100644 --- a/gyp/samples.gypi +++ b/gyp/samples.gypi @@ -96,6 +96,7 @@ '../samplecode/SampleStringArt.cpp', '../samplecode/SampleStrokePath.cpp', '../samplecode/SampleSubpixelTranslate.cpp', + '../samplecode/SampleSVGPong.cpp', '../samplecode/SampleSVGFile.cpp', '../samplecode/SampleText.cpp', '../samplecode/SampleTextAlpha.cpp', diff --git a/samplecode/SampleSVGPong.cpp b/samplecode/SampleSVGPong.cpp new file mode 100644 index 0000000000..be107b64dc --- /dev/null +++ b/samplecode/SampleSVGPong.cpp @@ -0,0 +1,286 @@ +/* + * 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(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 { + 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); |