Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2020 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/SkMatrix44.h" |
| 10 | #include "include/core/SkPaint.h" |
| 11 | #include "include/core/SkRRect.h" |
Mike Reed | 7543587 | 2020-01-13 21:15:06 -0500 | [diff] [blame] | 12 | #include "include/private/SkM44.h" |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 13 | #include "include/utils/Sk3D.h" |
| 14 | #include "include/utils/SkRandom.h" |
| 15 | #include "samplecode/Sample.h" |
| 16 | #include "tools/Resources.h" |
| 17 | |
| 18 | static SkMatrix44 inv(const SkMatrix44& m) { |
| 19 | SkMatrix44 inverse; |
| 20 | SkAssertResult(m.invert(&inverse)); |
| 21 | return inverse; |
| 22 | } |
| 23 | |
| 24 | class Sample3DView : public Sample { |
| 25 | protected: |
| 26 | float fNear = 0.05f; |
| 27 | float fFar = 4; |
| 28 | float fAngle = SK_ScalarPI / 12; |
| 29 | |
| 30 | SkPoint3 fEye { 0, 0, 1.0f/tan(fAngle/2) - 1 }; |
| 31 | SkPoint3 fCOA { 0, 0, 0 }; |
| 32 | SkPoint3 fUp { 0, 1, 0 }; |
| 33 | |
| 34 | SkMatrix44 fRot; |
| 35 | SkPoint3 fTrans; |
| 36 | |
| 37 | void rotate(float x, float y, float z) { |
| 38 | SkMatrix44 r; |
| 39 | if (x) { |
| 40 | r.setRotateAboutUnit(1, 0, 0, x); |
| 41 | } else if (y) { |
| 42 | r.setRotateAboutUnit(0, 1, 0, y); |
| 43 | } else { |
| 44 | r.setRotateAboutUnit(0, 0, 1, z); |
| 45 | } |
| 46 | fRot.postConcat(r); |
| 47 | } |
| 48 | |
| 49 | public: |
Mike Reed | ee0a03a | 2020-01-14 16:44:47 -0500 | [diff] [blame] | 50 | void saveCamera(SkCanvas* canvas, const SkRect& area, SkScalar zscale) { |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 51 | SkMatrix44 camera, |
| 52 | perspective, |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 53 | viewport; |
| 54 | |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 55 | Sk3Perspective(&perspective, fNear, fFar, fAngle); |
| 56 | Sk3LookAt(&camera, fEye, fCOA, fUp); |
Mike Reed | 7543587 | 2020-01-13 21:15:06 -0500 | [diff] [blame] | 57 | viewport.setScale(area.width()*0.5f, area.height()*0.5f, zscale) |
| 58 | .postTranslate(area.centerX(), area.centerY(), 0); |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 59 | |
Mike Reed | ee0a03a | 2020-01-14 16:44:47 -0500 | [diff] [blame] | 60 | canvas->saveCamera(viewport * perspective, camera * inv(viewport)); |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 61 | } |
| 62 | |
| 63 | bool onChar(SkUnichar uni) override { |
| 64 | float delta = SK_ScalarPI / 30; |
| 65 | switch (uni) { |
| 66 | case '8': this->rotate( delta, 0, 0); return true; |
| 67 | case '2': this->rotate(-delta, 0, 0); return true; |
| 68 | case '4': this->rotate(0, delta, 0); return true; |
| 69 | case '6': this->rotate(0, -delta, 0); return true; |
| 70 | case '-': this->rotate(0, 0, delta); return true; |
| 71 | case '+': this->rotate(0, 0, -delta); return true; |
| 72 | |
| 73 | case 'i': fTrans.fZ += 0.1f; SkDebugf("z %g\n", fTrans.fZ); return true; |
| 74 | case 'k': fTrans.fZ -= 0.1f; SkDebugf("z %g\n", fTrans.fZ); return true; |
| 75 | |
| 76 | case 'n': fNear += 0.1f; SkDebugf("near %g\n", fNear); return true; |
| 77 | case 'N': fNear -= 0.1f; SkDebugf("near %g\n", fNear); return true; |
| 78 | case 'f': fFar += 0.1f; SkDebugf("far %g\n", fFar); return true; |
| 79 | case 'F': fFar -= 0.1f; SkDebugf("far %g\n", fFar); return true; |
| 80 | default: break; |
| 81 | } |
| 82 | return false; |
| 83 | } |
| 84 | }; |
| 85 | |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 86 | static SkMatrix44 RX(SkScalar rad) { |
| 87 | SkScalar c = SkScalarCos(rad), s = SkScalarSin(rad); |
| 88 | SkMatrix44 m; |
| 89 | m.set3x3(1, 0, 0, |
| 90 | 0, c, s, |
| 91 | 0,-s, c); |
| 92 | return m; |
| 93 | } |
| 94 | |
| 95 | static SkMatrix44 RY(SkScalar rad) { |
| 96 | SkScalar c = SkScalarCos(rad), s = SkScalarSin(rad); |
| 97 | SkMatrix44 m; |
| 98 | m.set3x3( c, 0,-s, |
| 99 | 0, 1, 0, |
| 100 | s, 0, c); |
| 101 | return m; |
| 102 | } |
| 103 | |
| 104 | struct Face { |
| 105 | SkScalar fRx, fRy; |
| 106 | |
| 107 | static SkMatrix44 T(SkScalar x, SkScalar y, SkScalar z) { |
| 108 | SkMatrix44 m; |
| 109 | m.setTranslate(x, y, z); |
| 110 | return m; |
| 111 | } |
| 112 | |
| 113 | static SkMatrix44 R(SkScalar x, SkScalar y, SkScalar z, SkScalar rad) { |
| 114 | SkMatrix44 m; |
| 115 | m.setRotateAboutUnit(x, y, z, rad); |
| 116 | return m; |
| 117 | } |
| 118 | |
| 119 | SkMatrix44 asM44(SkScalar scale) const { |
| 120 | return RY(fRy) * RX(fRx) * T(0, 0, scale); |
| 121 | } |
| 122 | }; |
| 123 | |
Mike Reed | 7543587 | 2020-01-13 21:15:06 -0500 | [diff] [blame] | 124 | static bool front(const SkM44& m) { |
| 125 | SkM44 m2; |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 126 | m.invert(&m2); |
Mike Reed | 7543587 | 2020-01-13 21:15:06 -0500 | [diff] [blame] | 127 | /* |
| 128 | * Classically we want to dot the transpose(inverse(ctm)) with our surface normal. |
| 129 | * In this case, the normal is known to be {0, 0, 1}, so we only actually need to look |
| 130 | * at the z-scale of the inverse (the transpose doesn't change the main diagonal, so |
| 131 | * no need to actually transpose). |
| 132 | */ |
| 133 | return m2.atColMajor(10) > 0; |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 134 | } |
| 135 | |
| 136 | const Face faces[] = { |
| 137 | { 0, 0 }, // front |
| 138 | { 0, SK_ScalarPI }, // back |
| 139 | |
| 140 | { SK_ScalarPI/2, 0 }, // top |
| 141 | {-SK_ScalarPI/2, 0 }, // bottom |
| 142 | |
| 143 | { 0, SK_ScalarPI/2 }, // left |
| 144 | { 0,-SK_ScalarPI/2 }, // right |
| 145 | }; |
| 146 | |
Mike Reed | b18e74d | 2020-01-16 13:58:22 -0500 | [diff] [blame^] | 147 | #include "include/core/SkColorFilter.h" |
| 148 | #include "include/effects/SkColorMatrix.h" |
| 149 | |
| 150 | static SkV3 normalize(SkV3 v) { return v * (1.0f / v.length()); } |
| 151 | |
| 152 | static SkColorMatrix comput_planar_lighting(SkCanvas* canvas, SkV3 lightDir) { |
| 153 | SkM44 l2w = canvas->getLocalToWorld(); |
| 154 | auto normal = normalize(l2w * SkV3{0, 0, 1}); |
| 155 | float dot = -normal * lightDir; |
| 156 | |
| 157 | SkColorMatrix cm; |
| 158 | if (dot < 0) { |
| 159 | dot = 0; |
| 160 | } |
| 161 | |
| 162 | float ambient = 0.5f; |
| 163 | float scale = ambient + dot; |
| 164 | cm.setScale(scale, scale, scale, 1); |
| 165 | return cm; |
| 166 | } |
| 167 | |
| 168 | struct Light { |
| 169 | SkPoint fCenter; |
| 170 | SkPoint fEndPt; |
| 171 | SkScalar fRadius; |
| 172 | SkScalar fHeight; |
| 173 | |
| 174 | bool hitTest(SkScalar x, SkScalar y) const { |
| 175 | auto xx = x - fCenter.fX; |
| 176 | auto yy = y - fCenter.fY; |
| 177 | return xx*xx + yy*yy <= fRadius*fRadius; |
| 178 | } |
| 179 | |
| 180 | void update(SkScalar x, SkScalar y) { |
| 181 | auto xx = x - fCenter.fX; |
| 182 | auto yy = y - fCenter.fY; |
| 183 | auto len = SkScalarSqrt(xx*xx + yy*yy); |
| 184 | if (len > fRadius) { |
| 185 | xx *= fRadius / len; |
| 186 | yy *= fRadius / len; |
| 187 | } |
| 188 | fEndPt = {fCenter.fX + xx, fCenter.fY + yy}; |
| 189 | } |
| 190 | |
| 191 | SkV3 getDir() const { |
| 192 | auto pt = fEndPt - fCenter; |
| 193 | return normalize({pt.fX, pt.fY, -fHeight}); |
| 194 | } |
| 195 | |
| 196 | void draw(SkCanvas* canvas) { |
| 197 | SkPaint paint; |
| 198 | paint.setAntiAlias(true); |
| 199 | canvas->drawCircle(fCenter.fX, fCenter.fY, 5, paint); |
| 200 | paint.setStyle(SkPaint::kStroke_Style); |
| 201 | canvas->drawCircle(fCenter.fX, fCenter.fY, fRadius, paint); |
| 202 | paint.setColor(SK_ColorRED); |
| 203 | canvas->drawLine(fCenter.fX, fCenter.fY, fEndPt.fX, fEndPt.fY, paint); |
| 204 | } |
| 205 | }; |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 206 | |
| 207 | class SampleRR3D : public Sample3DView { |
| 208 | SkRRect fRR; |
Mike Reed | b18e74d | 2020-01-16 13:58:22 -0500 | [diff] [blame^] | 209 | Light fLight = { |
| 210 | {60, 60}, {60, 60}, 50, 10 |
| 211 | }; |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 212 | sk_sp<SkShader> fShader; |
| 213 | |
| 214 | SkString name() override { return SkString("rrect3d"); } |
| 215 | |
| 216 | void onOnceBeforeDraw() override { |
| 217 | fRR = SkRRect::MakeRectXY({20, 20, 380, 380}, 50, 50); |
| 218 | fShader = GetResourceAsImage("images/mandrill_128.png") |
| 219 | ->makeShader(SkMatrix::MakeScale(3, 3)); |
| 220 | } |
| 221 | |
| 222 | bool onChar(SkUnichar uni) override { |
| 223 | return this->Sample3DView::onChar(uni); |
| 224 | } |
| 225 | |
| 226 | void drawContent(SkCanvas* canvas, const SkMatrix44& m) { |
Mike Reed | 7543587 | 2020-01-13 21:15:06 -0500 | [diff] [blame] | 227 | SkMatrix44 trans; |
| 228 | trans.setTranslate(200, 200, 0); // center of the rotation |
| 229 | |
| 230 | canvas->concat(trans * fRot * m * inv(trans)); |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 231 | |
Mike Reed | b18e74d | 2020-01-16 13:58:22 -0500 | [diff] [blame^] | 232 | if (!front(canvas->getLocalToDevice())) { |
| 233 | return; |
| 234 | } |
| 235 | |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 236 | SkPaint paint; |
Mike Reed | b18e74d | 2020-01-16 13:58:22 -0500 | [diff] [blame^] | 237 | paint.setAlphaf(front(canvas->getLocalToDevice()) ? 1 : 0.25f); |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 238 | paint.setShader(fShader); |
Mike Reed | b18e74d | 2020-01-16 13:58:22 -0500 | [diff] [blame^] | 239 | |
| 240 | SkColorMatrix cm = comput_planar_lighting(canvas, fLight.getDir()); |
| 241 | paint.setColorFilter(SkColorFilters::Matrix(cm)); |
| 242 | |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 243 | canvas->drawRRect(fRR, paint); |
| 244 | } |
| 245 | |
| 246 | void onDrawContent(SkCanvas* canvas) override { |
Mike Reed | b18e74d | 2020-01-16 13:58:22 -0500 | [diff] [blame^] | 247 | canvas->save(); |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 248 | canvas->translate(400, 300); |
| 249 | |
Mike Reed | ee0a03a | 2020-01-14 16:44:47 -0500 | [diff] [blame] | 250 | this->saveCamera(canvas, {0, 0, 400, 400}, 200); |
Mike Reed | 7543587 | 2020-01-13 21:15:06 -0500 | [diff] [blame] | 251 | |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 252 | for (auto f : faces) { |
| 253 | SkAutoCanvasRestore acr(canvas, true); |
Mike Reed | 7543587 | 2020-01-13 21:15:06 -0500 | [diff] [blame] | 254 | this->drawContent(canvas, f.asM44(200)); |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 255 | } |
Mike Reed | ee0a03a | 2020-01-14 16:44:47 -0500 | [diff] [blame] | 256 | |
| 257 | canvas->restore(); |
Mike Reed | b18e74d | 2020-01-16 13:58:22 -0500 | [diff] [blame^] | 258 | canvas->restore(); |
| 259 | |
| 260 | fLight.draw(canvas); |
| 261 | } |
| 262 | |
| 263 | Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override { |
| 264 | if (fLight.hitTest(x, y)) { |
| 265 | return new Click(); |
| 266 | } |
| 267 | return nullptr; |
| 268 | } |
| 269 | bool onClick(Click* click) override { |
| 270 | fLight.update(click->fCurr.fX, click->fCurr.fY); |
| 271 | return true; |
Mike Reed | 69ace2a | 2020-01-11 15:57:14 -0500 | [diff] [blame] | 272 | } |
| 273 | }; |
| 274 | DEF_SAMPLE( return new SampleRR3D(); ) |