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 "include/core/SkCanvas.h"
9 #include "include/core/SkPathBuilder.h"
10 #include "include/core/SkRRect.h"
11 #include "include/private/base/SkTPin.h"
12 #include "modules/sksg/include/SkSGDraw.h"
13 #include "modules/sksg/include/SkSGGroup.h"
14 #include "modules/sksg/include/SkSGInvalidationController.h"
15 #include "modules/sksg/include/SkSGPaint.h"
16 #include "modules/sksg/include/SkSGPath.h"
17 #include "modules/sksg/include/SkSGRect.h"
18 #include "modules/sksg/include/SkSGScene.h"
19 #include "modules/sksg/include/SkSGTransform.h"
20 #include "src/base/SkRandom.h"
21 #include "tools/timer/TimeUtils.h"
22 #include "tools/viewer/Slide.h"
23
24 namespace {
25
26 static const SkRect kBounds = SkRect::MakeLTRB(0.1f, 0.1f, 0.9f, 0.9f);
27 static const SkSize kPaddleSize = SkSize::Make(0.03f, 0.1f);
28 static const SkScalar kBallSize = 0.04f;
29 static const SkScalar kShadowOpacity = 0.40f;
30 static const SkScalar kShadowParallax = 0.04f;
31 static const SkScalar kBackgroundStroke = 0.01f;
32 static const uint32_t kBackgroundDashCount = 20;
33
34 static const SkScalar kBallSpeedMax = 0.0020f;
35 static const SkScalar kBallSpeedMin = 0.0005f;
36 static const SkScalar kBallSpeedFuzz = 0.0002f;
37
38 static const SkScalar kTimeScaleMin = 0.0f;
39 static const SkScalar kTimeScaleMax = 5.0f;
40
41 // Box the value within [min, max), by applying infinite reflection on the interval endpoints.
box_reflect(SkScalar v,SkScalar min,SkScalar max)42 SkScalar box_reflect(SkScalar v, SkScalar min, SkScalar max) {
43 const SkScalar intervalLen = max - min;
44 SkASSERT(intervalLen > 0);
45
46 // f(v) is periodic in 2 * intervalLen: one normal progression + one reflection
47 const SkScalar P = intervalLen * 2;
48 // relative to P origin
49 const SkScalar vP = v - min;
50 // map to [0, P)
51 const SkScalar vMod = (vP < 0) ? P - SkScalarMod(-vP, P) : SkScalarMod(vP, P);
52 // reflect if needed, to map to [0, intervalLen)
53 const SkScalar vInterval = vMod < intervalLen ? vMod : P - vMod;
54 // finally, reposition relative to min
55 return vInterval + min;
56 }
57
58 // Compute <t, y> for the trajectory intersection with the next vertical edge.
find_yintercept(const SkPoint & pos,const SkVector & spd,const SkRect & box)59 std::tuple<SkScalar, SkScalar> find_yintercept(const SkPoint& pos, const SkVector& spd,
60 const SkRect& box) {
61 const SkScalar edge = spd.fX > 0 ? box.fRight : box.fLeft;
62 const SkScalar t = (edge - pos.fX) / spd.fX;
63 SkASSERT(t >= 0);
64 const SkScalar dY = t * spd.fY;
65
66 return std::make_tuple(t, box_reflect(pos.fY + dY, box.fTop, box.fBottom));
67 }
68
update_pos(const sk_sp<sksg::RRect> & rr,const SkPoint & pos)69 void update_pos(const sk_sp<sksg::RRect>& rr, const SkPoint& pos) {
70 // TODO: position setters on RRect?
71
72 const auto r = rr->getRRect().rect();
73 const auto offsetX = pos.x() - r.x(),
74 offsetY = pos.y() - r.y();
75 rr->setRRect(rr->getRRect().makeOffset(offsetX, offsetY));
76 }
77
78 } // namespace
79
80 class PongSlide final : public Slide {
81 public:
PongSlide()82 PongSlide() { fName = "SGPong"; }
83
load(SkScalar w,SkScalar h)84 void load(SkScalar w, SkScalar h) override {
85 const SkRect fieldBounds = kBounds.makeOutset(kBallSize / 2, kBallSize / 2);
86 const SkRRect ball = SkRRect::MakeOval(SkRect::MakeWH(kBallSize, kBallSize));
87 const SkRRect paddle = SkRRect::MakeRectXY(SkRect::MakeWH(kPaddleSize.width(),
88 kPaddleSize.height()),
89 kPaddleSize.width() / 2,
90 kPaddleSize.width() / 2);
91 fBall.initialize(ball,
92 SkPoint::Make(kBounds.centerX(), kBounds.centerY()),
93 SkVector::Make(fRand.nextRangeScalar(kBallSpeedMin, kBallSpeedMax),
94 fRand.nextRangeScalar(kBallSpeedMin, kBallSpeedMax)));
95 fPaddle0.initialize(paddle,
96 SkPoint::Make(fieldBounds.left() - kPaddleSize.width() / 2,
97 fieldBounds.centerY()),
98 SkVector::Make(0, 0));
99 fPaddle1.initialize(paddle,
100 SkPoint::Make(fieldBounds.right() + kPaddleSize.width() / 2,
101 fieldBounds.centerY()),
102 SkVector::Make(0, 0));
103
104 // Background decoration.
105 SkPathBuilder bgPath;
106 bgPath.moveTo(kBounds.left() , fieldBounds.top())
107 .lineTo(kBounds.right(), fieldBounds.top())
108 .moveTo(kBounds.left() , fieldBounds.bottom())
109 .lineTo(kBounds.right(), fieldBounds.bottom());
110 // TODO: stroke-dash support would come in handy right about now.
111 for (uint32_t i = 0; i < kBackgroundDashCount; ++i) {
112 bgPath.moveTo(kBounds.centerX(),
113 kBounds.top() + (i + 0.25f) * kBounds.height() / kBackgroundDashCount)
114 .lineTo(kBounds.centerX(),
115 kBounds.top() + (i + 0.75f) * kBounds.height() / kBackgroundDashCount);
116 }
117
118 auto bg_path = sksg::Path::Make(bgPath.detach());
119 auto bg_paint = sksg::Color::Make(SK_ColorBLACK);
120 bg_paint->setStyle(SkPaint::kStroke_Style);
121 bg_paint->setStrokeWidth(kBackgroundStroke);
122
123 auto ball_paint = sksg::Color::Make(SK_ColorGREEN),
124 paddle0_paint = sksg::Color::Make(SK_ColorBLUE),
125 paddle1_paint = sksg::Color::Make(SK_ColorRED),
126 shadow_paint = sksg::Color::Make(SK_ColorBLACK);
127 ball_paint->setAntiAlias(true);
128 paddle0_paint->setAntiAlias(true);
129 paddle1_paint->setAntiAlias(true);
130 shadow_paint->setAntiAlias(true);
131 shadow_paint->setOpacity(kShadowOpacity);
132
133 // Build the scene graph.
134 auto group = sksg::Group::Make();
135 group->addChild(sksg::Draw::Make(std::move(bg_path), std::move(bg_paint)));
136 group->addChild(sksg::Draw::Make(fPaddle0.shadowNode, shadow_paint));
137 group->addChild(sksg::Draw::Make(fPaddle1.shadowNode, shadow_paint));
138 group->addChild(sksg::Draw::Make(fBall.shadowNode, shadow_paint));
139 group->addChild(sksg::Draw::Make(fPaddle0.objectNode, paddle0_paint));
140 group->addChild(sksg::Draw::Make(fPaddle1.objectNode, paddle1_paint));
141 group->addChild(sksg::Draw::Make(fBall.objectNode, ball_paint));
142
143 // Handle everything in a normalized 1x1 space.
144 fContentMatrix = sksg::Matrix<SkMatrix>::Make(
145 SkMatrix::RectToRect(SkRect::MakeWH(1, 1),
146 SkRect::MakeWH(w, h)));
147 auto root = sksg::TransformEffect::Make(std::move(group), fContentMatrix);
148 fScene = sksg::Scene::Make(std::move(root));
149
150 // Off we go.
151 this->updatePaddleStrategy();
152 }
153
onChar(SkUnichar uni)154 bool onChar(SkUnichar uni) override {
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 case 'I':
163 fShowInval = !fShowInval;
164 return true;
165 default:
166 break;
167 }
168 return false;
169 }
170
resize(SkScalar w,SkScalar h)171 void resize(SkScalar w, SkScalar h) override {
172 if (fContentMatrix) {
173 fContentMatrix->setMatrix(SkMatrix::RectToRect(SkRect::MakeWH(1, 1),
174 SkRect::MakeWH(w, h)));
175 }
176 }
177
draw(SkCanvas * canvas)178 void draw(SkCanvas* canvas) override {
179 sksg::InvalidationController ic;
180 fScene->render(canvas);
181
182 if (fShowInval) {
183 SkPaint fill, stroke;
184 fill.setAntiAlias(true);
185 fill.setColor(0x40ff0000);
186 stroke.setAntiAlias(true);
187 stroke.setColor(0xffff0000);
188 stroke.setStyle(SkPaint::kStroke_Style);
189
190 for (const auto& r : ic) {
191 canvas->drawRect(r, fill);
192 canvas->drawRect(r, stroke);
193 }
194 }
195 }
196
animate(double nanos)197 bool animate(double nanos) override {
198 // onAnimate may fire before the first draw.
199 if (fScene) {
200 SkScalar dt = (TimeUtils::NanosToMSec(nanos) - fLastTick) * fTimeScale;
201 fLastTick = TimeUtils::NanosToMSec(nanos);
202
203 fPaddle0.posTick(dt);
204 fPaddle1.posTick(dt);
205 fBall.posTick(dt);
206
207 this->enforceConstraints();
208
209 fPaddle0.updateDom();
210 fPaddle1.updateDom();
211 fBall.updateDom();
212 }
213 return true;
214 }
215
216 private:
217 struct Object {
initializePongSlide::Object218 void initialize(const SkRRect& rrect, const SkPoint& p, const SkVector& s) {
219 objectNode = sksg::RRect::Make(rrect);
220 shadowNode = sksg::RRect::Make(rrect);
221
222 pos = p;
223 spd = s;
224 size = SkSize::Make(rrect.width(), rrect.height());
225 }
226
posTickPongSlide::Object227 void posTick(SkScalar dt) {
228 pos += spd * dt;
229 }
230
updateDomPongSlide::Object231 void updateDom() {
232 const SkPoint corner = pos - SkPoint::Make(size.width() / 2, size.height() / 2);
233 update_pos(objectNode, corner);
234
235 // Simulate parallax shadow for a centered light source.
236 SkPoint shadowOffset = pos - SkPoint::Make(kBounds.centerX(), kBounds.centerY());
237 shadowOffset.scale(kShadowParallax);
238 const SkPoint shadowCorner = corner + shadowOffset;
239
240 update_pos(shadowNode, shadowCorner);
241 }
242
243 sk_sp<sksg::RRect> objectNode,
244 shadowNode;
245 SkPoint pos;
246 SkVector spd;
247 SkSize size;
248 };
249
enforceConstraints()250 void enforceConstraints() {
251 // Perfect vertical reflection.
252 if (fBall.pos.fY < kBounds.fTop || fBall.pos.fY >= kBounds.fBottom) {
253 fBall.spd.fY = -fBall.spd.fY;
254 fBall.pos.fY = box_reflect(fBall.pos.fY, kBounds.fTop, kBounds.fBottom);
255 }
256
257 // Horizontal bounce - introduces a speed fuzz.
258 if (fBall.pos.fX < kBounds.fLeft || fBall.pos.fX >= kBounds.fRight) {
259 fBall.spd.fX = this->fuzzBallSpeed(-fBall.spd.fX);
260 fBall.spd.fY = this->fuzzBallSpeed(fBall.spd.fY);
261 fBall.pos.fX = box_reflect(fBall.pos.fX, kBounds.fLeft, kBounds.fRight);
262 this->updatePaddleStrategy();
263 }
264 }
265
fuzzBallSpeed(SkScalar spd)266 SkScalar fuzzBallSpeed(SkScalar spd) {
267 // The speed limits are absolute values.
268 const SkScalar sign = spd >= 0 ? 1.0f : -1.0f;
269 const SkScalar fuzzed = fabs(spd) + fRand.nextRangeScalar(-kBallSpeedFuzz, kBallSpeedFuzz);
270
271 return sign * SkTPin(fuzzed, kBallSpeedMin, kBallSpeedMax);
272 }
273
updatePaddleStrategy()274 void updatePaddleStrategy() {
275 Object* pitcher = fBall.spd.fX > 0 ? &fPaddle0 : &fPaddle1;
276 Object* catcher = fBall.spd.fX > 0 ? &fPaddle1 : &fPaddle0;
277
278 SkScalar t, yIntercept;
279 std::tie(t, yIntercept) = find_yintercept(fBall.pos, fBall.spd, kBounds);
280
281 // The pitcher aims for a neutral/centered position.
282 pitcher->spd.fY = (kBounds.centerY() - pitcher->pos.fY) / t;
283
284 // The catcher goes for the ball. Duh.
285 catcher->spd.fY = (yIntercept - catcher->pos.fY) / t;
286 }
287
288 std::unique_ptr<sksg::Scene> fScene;
289 sk_sp<sksg::Matrix<SkMatrix>> fContentMatrix;
290 Object fPaddle0, fPaddle1, fBall;
291 SkRandom fRand;
292
293 TimeUtils::MSec fLastTick = 0;
294 SkScalar fTimeScale = 1.0f;
295 bool fShowInval = false;
296 };
297
298 DEF_SLIDE( return new PongSlide(); )
299