• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "SampleCode.h"
9 #include "SkAnimTimer.h"
10 #include "SkColor.h"
11 #include "SkRandom.h"
12 #include "SkRRect.h"
13 #include "SkSVGDOM.h"
14 #include "SkSVGG.h"
15 #include "SkSVGPath.h"
16 #include "SkSVGRect.h"
17 #include "SkSVGSVG.h"
18 
19 namespace {
20 
21 static const SkRect kBounds     = SkRect::MakeLTRB(0.1f, 0.1f, 0.9f, 0.9f);
22 static const SkSize kPaddleSize = SkSize::Make(0.03f, 0.1f);
23 static const SkScalar kBallSize = 0.04f;
24 static const SkScalar kShadowOpacity       = 0.40f;
25 static const SkScalar kShadowParallax      = 0.04f;
26 static const SkScalar kBackgroundStroke    = 0.01f;
27 static const uint32_t kBackgroundDashCount = 20;
28 
29 static const SkScalar kBallSpeedMax  = 0.0020f;
30 static const SkScalar kBallSpeedMin  = 0.0005f;
31 static const SkScalar kBallSpeedFuzz = 0.0002f;
32 
33 static const SkScalar kTimeScaleMin = 0.0f;
34 static const SkScalar kTimeScaleMax = 5.0f;
35 
36 // Box the value within [min, max), by applying infinite reflection on the interval endpoints.
box_reflect(SkScalar v,SkScalar min,SkScalar max)37 SkScalar box_reflect(SkScalar v, SkScalar min, SkScalar max) {
38     const SkScalar intervalLen = max - min;
39     SkASSERT(intervalLen > 0);
40 
41     // f(v) is periodic in 2 * intervalLen: one normal progression + one reflection
42     const SkScalar P = intervalLen * 2;
43     // relative to P origin
44     const SkScalar vP = v - min;
45     // map to [0, P)
46     const SkScalar vMod = (vP < 0) ? P - SkScalarMod(-vP, P) : SkScalarMod(vP, P);
47     // reflect if needed, to map to [0, intervalLen)
48     const SkScalar vInterval = vMod < intervalLen ? vMod : P - vMod;
49     // finally, reposition relative to min
50     return vInterval + min;
51 }
52 
53 // Compute <t, y> for the trajectory intersection with the next vertical edge.
find_yintercept(const SkPoint & pos,const SkVector & spd,const SkRect & box)54 std::tuple<SkScalar, SkScalar> find_yintercept(const SkPoint& pos, const SkVector& spd,
55                                                const SkRect& box) {
56     const SkScalar edge = spd.fX > 0 ? box.fRight : box.fLeft;
57     const SkScalar    t = (edge - pos.fX) / spd.fX;
58     SkASSERT(t >= 0);
59     const SkScalar   dY = t * spd.fY;
60 
61     return std::make_tuple(t, box_reflect(pos.fY + dY, box.fTop, box.fBottom));
62 }
63 
make_svg_rrect(const SkRRect & rrect)64 sk_sp<SkSVGRect> make_svg_rrect(const SkRRect& rrect) {
65     sk_sp<SkSVGRect> node = SkSVGRect::Make();
66     node->setX(SkSVGLength(rrect.rect().x()));
67     node->setY(SkSVGLength(rrect.rect().y()));
68     node->setWidth(SkSVGLength(rrect.width()));
69     node->setHeight(SkSVGLength(rrect.height()));
70     node->setRx(SkSVGLength(rrect.getSimpleRadii().x()));
71     node->setRy(SkSVGLength(rrect.getSimpleRadii().y()));
72 
73     return node;
74 }
75 
76 } // anonymous ns
77 
78 class SVGPongView final : public SampleView {
79 public:
SVGPongView()80     SVGPongView() {}
81 
82 protected:
onOnceBeforeDraw()83     void onOnceBeforeDraw() override {
84         const SkRect fieldBounds = kBounds.makeOutset(kBallSize / 2, kBallSize / 2);
85         const SkRRect ball = SkRRect::MakeOval(SkRect::MakeWH(kBallSize, kBallSize));
86         const SkRRect paddle = SkRRect::MakeRectXY(SkRect::MakeWH(kPaddleSize.width(),
87                                                                   kPaddleSize.height()),
88                                                    kPaddleSize.width() / 2,
89                                                    kPaddleSize.width() / 2);
90         fBall.initialize(ball,
91                          SK_ColorGREEN,
92                          SkPoint::Make(kBounds.centerX(), kBounds.centerY()),
93                          SkVector::Make(fRand.nextRangeScalar(kBallSpeedMin, kBallSpeedMax),
94                                         fRand.nextRangeScalar(kBallSpeedMin, kBallSpeedMax)));
95         fPaddle0.initialize(paddle,
96                             SK_ColorBLUE,
97                             SkPoint::Make(fieldBounds.left() - kPaddleSize.width() / 2,
98                                           fieldBounds.centerY()),
99                             SkVector::Make(0, 0));
100         fPaddle1.initialize(paddle,
101                             SK_ColorRED,
102                             SkPoint::Make(fieldBounds.right() + kPaddleSize.width() / 2,
103                                           fieldBounds.centerY()),
104                             SkVector::Make(0, 0));
105 
106         // Background decoration.
107         SkPath bgPath;
108         bgPath.moveTo(kBounds.left() , fieldBounds.top());
109         bgPath.lineTo(kBounds.right(), fieldBounds.top());
110         bgPath.moveTo(kBounds.left() , fieldBounds.bottom());
111         bgPath.lineTo(kBounds.right(), fieldBounds.bottom());
112         // TODO: stroke-dash support would come in handy right about now.
113         for (uint32_t i = 0; i < kBackgroundDashCount; ++i) {
114             bgPath.moveTo(kBounds.centerX(),
115                           kBounds.top() + (i + 0.25f) * kBounds.height() / kBackgroundDashCount);
116             bgPath.lineTo(kBounds.centerX(),
117                           kBounds.top() + (i + 0.75f) * kBounds.height() / kBackgroundDashCount);
118         }
119 
120         sk_sp<SkSVGPath> bg = SkSVGPath::Make();
121         bg->setPath(bgPath);
122         bg->setFill(SkSVGPaint(SkSVGPaint::Type::kNone));
123         bg->setStroke(SkSVGPaint(SkSVGColorType(SK_ColorBLACK)));
124         bg->setStrokeWidth(SkSVGLength(kBackgroundStroke));
125 
126         // Build the SVG DOM tree.
127         sk_sp<SkSVGSVG> root = SkSVGSVG::Make();
128         root->appendChild(std::move(bg));
129         root->appendChild(fPaddle0.shadowNode);
130         root->appendChild(fPaddle1.shadowNode);
131         root->appendChild(fBall.shadowNode);
132         root->appendChild(fPaddle0.objectNode);
133         root->appendChild(fPaddle1.objectNode);
134         root->appendChild(fBall.objectNode);
135 
136         // Handle everything in a normalized 1x1 space.
137         root->setViewBox(SkSVGViewBoxType(SkRect::MakeWH(1, 1)));
138 
139         fDom = sk_sp<SkSVGDOM>(new SkSVGDOM());
140         fDom->setContainerSize(SkSize::Make(this->width(), this->height()));
141         fDom->setRoot(std::move(root));
142 
143         // Off we go.
144         this->updatePaddleStrategy();
145     }
146 
onQuery(SkEvent * evt)147     bool onQuery(SkEvent* evt) override {
148         if (SampleCode::TitleQ(*evt)) {
149             SampleCode::TitleR(evt, "SVGPong");
150             return true;
151         }
152 
153         SkUnichar uni;
154         if (SampleCode::CharQ(*evt, &uni)) {
155             switch (uni) {
156                 case '[':
157                     fTimeScale = SkTPin(fTimeScale - 0.1f, kTimeScaleMin, kTimeScaleMax);
158                     return true;
159                 case ']':
160                     fTimeScale = SkTPin(fTimeScale + 0.1f, kTimeScaleMin, kTimeScaleMax);
161                     return true;
162                 default:
163                     break;
164             }
165         }
166         return this->INHERITED::onQuery(evt);
167     }
168 
onSizeChange()169     void onSizeChange() override {
170         if (fDom) {
171             fDom->setContainerSize(SkSize::Make(this->width(), this->height()));
172         }
173 
174         this->INHERITED::onSizeChange();
175     }
176 
onDrawContent(SkCanvas * canvas)177     void onDrawContent(SkCanvas* canvas) override {
178         fDom->render(canvas);
179     }
180 
onAnimate(const SkAnimTimer & timer)181     bool onAnimate(const SkAnimTimer& timer) override {
182         SkScalar dt = (timer.msec() - fLastTick) * fTimeScale;
183         fLastTick = timer.msec();
184 
185         fPaddle0.posTick(dt);
186         fPaddle1.posTick(dt);
187         fBall.posTick(dt);
188 
189         this->enforceConstraints();
190 
191         fPaddle0.updateDom();
192         fPaddle1.updateDom();
193         fBall.updateDom();
194 
195         return true;
196     }
197 
198 private:
199     struct Object {
initializeSVGPongView::Object200         void initialize(const SkRRect& rrect, SkColor color,
201                         const SkPoint& p, const SkVector& s) {
202             objectNode = make_svg_rrect(rrect);
203             objectNode->setFill(SkSVGPaint(SkSVGColorType(color)));
204 
205             shadowNode = make_svg_rrect(rrect);
206             shadowNode->setFillOpacity(SkSVGNumberType(kShadowOpacity));
207 
208             pos = p;
209             spd = s;
210             size = SkSize::Make(rrect.width(), rrect.height());
211         }
212 
posTickSVGPongView::Object213         void posTick(SkScalar dt) {
214             pos += spd * dt;
215         }
216 
updateDomSVGPongView::Object217         void updateDom() {
218             const SkPoint corner = pos - SkPoint::Make(size.width() / 2, size.height() / 2);
219             objectNode->setX(SkSVGLength(corner.x()));
220             objectNode->setY(SkSVGLength(corner.y()));
221 
222             // Simulate parallax shadow for a centered light source.
223             SkPoint shadowOffset = pos - SkPoint::Make(kBounds.centerX(), kBounds.centerY());
224             shadowOffset.scale(kShadowParallax);
225             const SkPoint shadowCorner = corner + shadowOffset;
226 
227             shadowNode->setX(SkSVGLength(shadowCorner.x()));
228             shadowNode->setY(SkSVGLength(shadowCorner.y()));
229         }
230 
231         sk_sp<SkSVGRect> objectNode;
232         sk_sp<SkSVGRect> shadowNode;
233         SkPoint          pos;
234         SkVector         spd;
235         SkSize           size;
236     };
237 
enforceConstraints()238     void enforceConstraints() {
239         // Perfect vertical reflection.
240         if (fBall.pos.fY < kBounds.fTop || fBall.pos.fY >= kBounds.fBottom) {
241             fBall.spd.fY = -fBall.spd.fY;
242             fBall.pos.fY = box_reflect(fBall.pos.fY, kBounds.fTop, kBounds.fBottom);
243         }
244 
245         // Horizontal bounce - introduces a speed fuzz.
246         if (fBall.pos.fX < kBounds.fLeft || fBall.pos.fX >= kBounds.fRight) {
247             fBall.spd.fX = this->fuzzBallSpeed(-fBall.spd.fX);
248             fBall.spd.fY = this->fuzzBallSpeed(fBall.spd.fY);
249             fBall.pos.fX = box_reflect(fBall.pos.fX, kBounds.fLeft, kBounds.fRight);
250             this->updatePaddleStrategy();
251         }
252     }
253 
fuzzBallSpeed(SkScalar spd)254     SkScalar fuzzBallSpeed(SkScalar spd) {
255         // The speed limits are absolute values.
256         const SkScalar   sign = spd >= 0 ? 1.0f : -1.0f;
257         const SkScalar fuzzed = fabs(spd) + fRand.nextRangeScalar(-kBallSpeedFuzz, kBallSpeedFuzz);
258 
259         return sign * SkTPin(fuzzed, kBallSpeedMin, kBallSpeedMax);
260     }
261 
updatePaddleStrategy()262     void updatePaddleStrategy() {
263         Object* pitcher = fBall.spd.fX > 0 ? &fPaddle0 : &fPaddle1;
264         Object* catcher = fBall.spd.fX > 0 ? &fPaddle1 : &fPaddle0;
265 
266         SkScalar t, yIntercept;
267         std::tie(t, yIntercept) = find_yintercept(fBall.pos, fBall.spd, kBounds);
268 
269         // The pitcher aims for a neutral/centered position.
270         pitcher->spd.fY = (kBounds.centerY() - pitcher->pos.fY) / t;
271 
272         // The catcher goes for the ball.  Duh.
273         catcher->spd.fY = (yIntercept - catcher->pos.fY) / t;
274     }
275 
276     sk_sp<SkSVGDOM> fDom;
277     Object          fPaddle0, fPaddle1, fBall;
278     SkRandom        fRand;
279 
280     SkMSec          fLastTick  = 0;
281     SkScalar        fTimeScale = 1.0f;
282 
283     typedef SampleView INHERITED;
284 };
285 
SVGPongFactory()286 static SkView* SVGPongFactory() { return new SVGPongView; }
287 static SkViewRegister reg(SVGPongFactory);
288