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