1 /*
2 * Copyright 2023 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/SkBlendMode.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkCubicMap.h"
12 #include "include/core/SkImage.h"
13 #include "include/core/SkPoint.h"
14 #include "include/core/SkSamplingOptions.h"
15 #include "include/core/SkScalar.h"
16 #include "include/core/SkShader.h"
17 #include "include/core/SkTileMode.h"
18 #include "include/core/SkVertices.h"
19 #include "include/effects/SkGradientShader.h"
20 #include "include/private/base/SkFloatingPoint.h"
21 #include "tools/DecodeUtils.h"
22 #include "tools/Resources.h"
23 #include "tools/timer/TimeUtils.h"
24 #include "tools/viewer/Slide.h"
25
26 #include <cmath>
27 #include <cstddef>
28 #include <cstdint>
29 #include <iterator>
30 #include <vector>
31
32 #include "imgui.h"
33
34 namespace {
35
lerp(float a,float b,float t)36 float lerp(float a, float b, float t) {
37 return a + (b - a)*t;
38 }
39
make_image_shader(const char * resource)40 sk_sp<SkShader> make_image_shader(const char* resource) {
41 sk_sp<SkImage> img = ToolUtils::GetResourceAsImage(resource);
42
43 // Normalize to 1x1 for UV sampling.
44 const auto lm = SkMatrix::Scale(1.0f/img->width(), 1.0f/img->height());
45
46 return img->makeShader(SkTileMode::kDecal,
47 SkTileMode::kDecal,
48 SkSamplingOptions(SkFilterMode::kLinear),
49 &lm);
50 }
51
52 static constexpr struct ShaderFactory {
53 const char* fName;
54 sk_sp<SkShader> (*fBuild)();
55 } gShaderFactories[] = {
56 {
57 "Img (Mandrill)",
__anon58b8984e0202() 58 []() ->sk_sp<SkShader> { return make_image_shader("images/mandrill_512.png"); }
59 },
60 {
61 "Img (Baby Tux)",
__anon58b8984e0302() 62 []() ->sk_sp<SkShader> { return make_image_shader("images/baby_tux.png"); }
63 },
64 {
65 "Img (Brickwork)",
__anon58b8984e0402() 66 []() ->sk_sp<SkShader> { return make_image_shader("images/brickwork-texture.jpg"); }
67 },
68 {
69 "Radial Gradient",
__anon58b8984e0502() 70 []() ->sk_sp<SkShader> {
71 static constexpr SkColor gColors[] = {
72 SK_ColorGREEN, SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN
73 };
74 return SkGradientShader::MakeRadial({0.5f, 0.5f}, 0.5f, gColors, nullptr,
75 std::size(gColors), SkTileMode::kRepeat);
76 }
77 },
78 {
79 "Colors",
__anon58b8984e0602() 80 []() ->sk_sp<SkShader> { return nullptr; }
81 },
82 };
83
84 static constexpr struct VertexAnimator {
85 const char* fName;
86 void (*fAanimate)(const std::vector<SkPoint>& uv, float t, std::vector<SkPoint>& out);
87 } gVertexAnimators[] = {
88 {
89 "Cylinderator",
90 // Simulate a cylinder rolling sideways across the 1x1 uv space.
__anon58b8984e0702(const std::vector<SkPoint>& uvs, float t, std::vector<SkPoint>& out) 91 [](const std::vector<SkPoint>& uvs, float t, std::vector<SkPoint>& out) {
92 static constexpr float kCylRadius = .2f;
93
94 const auto cyl_pos = t;
95
96 for (size_t i = 0; i < uvs.size(); ++i) {
97 const auto& uv = uvs[i];
98
99 if (uv.fX <= cyl_pos) {
100 out[i] = uv;
101 continue;
102 }
103
104 const auto arc_len = uv.fX - cyl_pos,
105 arc_ang = arc_len/kCylRadius;
106
107 out[i] = SkPoint{
108 cyl_pos + std::sin(arc_ang)*kCylRadius,
109 uv.fY,
110 };
111 }
112 },
113 },
114 {
115 "Squircillator",
116 // Pull all vertices towards center, proportionally, such that the outer square edge
117 // is mapped to a circle for t == 1.
__anon58b8984e0802(const std::vector<SkPoint>& uvs, float t, std::vector<SkPoint>& out) 118 [](const std::vector<SkPoint>& uvs, float t, std::vector<SkPoint>& out) {
119 for (size_t i = 0; i < uvs.size(); ++i) {
120 // remap to [-.5,.5]
121 const auto uv = (uvs[i] - SkPoint{0.5,0.5});
122
123 // Distance from center to outer edge for the line pasing through uv.
124 const auto d = uv.length()*0.5f/std::max(std::abs(uv.fX), std::abs(uv.fY));
125 // Scale needed to pull the outer edge to the r=0.5 circle at t == 1.
126 const auto s = lerp(1, (0.5f / d), t);
127
128 out[i] = uv*s + SkPoint{0.5, 0.5};
129 }
130 },
131 },
132 {
133 "Twirlinator",
134 // Rotate vertices proportional to their distance to center.
__anon58b8984e0902(const std::vector<SkPoint>& uvs, float t, std::vector<SkPoint>& out) 135 [](const std::vector<SkPoint>& uvs, float t, std::vector<SkPoint>& out) {
136 static constexpr float kMaxRotate = SK_FloatPI*4;
137
138 for (size_t i = 0; i < uvs.size(); ++i) {
139 // remap to [-.5,.5]
140 const auto uv = (uvs[i] - SkPoint{0.5,0.5});
141 const auto angle = kMaxRotate * t * uv.length();
142
143 out[i] = SkMatrix::RotateRad(angle).mapPoint(uv) + SkPoint{0.5, 0.5};
144 }
145 },
146 },
147 {
148 "Wigglynator",
__anon58b8984e0a02(const std::vector<SkPoint>& uvs, float t, std::vector<SkPoint>& out) 149 [](const std::vector<SkPoint>& uvs, float t, std::vector<SkPoint>& out) {
150 const float radius = t*0.2f/(std::sqrt(uvs.size()) - 1);
151 for (size_t i = 0; i < uvs.size(); ++i) {
152 const float phase = i*SK_FloatPI*0.31f,
153 angle = phase + t*SK_FloatPI*2;
154 out[i] = uvs[i] + SkVector{
155 radius*std::cos(angle),
156 radius*std::sin(angle),
157 };
158 }
159 },
160 },
161 {
162 "None",
__anon58b8984e0b02(const std::vector<SkPoint>& uvs, float, std::vector<SkPoint>& out) 163 [](const std::vector<SkPoint>& uvs, float, std::vector<SkPoint>& out) { out = uvs; },
164 },
165 };
166
167 class MeshSlide final : public Slide {
168 public:
MeshSlide()169 MeshSlide() : fTimeMapper({0.5f, 0}, {0.5f, 1}) { fName = "Mesh"; }
170
load(SkScalar w,SkScalar h)171 void load(SkScalar w, SkScalar h) override {
172 fSize = {w, h};
173
174 this->initMesh(256);
175 this->initShader(gShaderFactories[0]);
176 }
177
resize(SkScalar w,SkScalar h)178 void resize(SkScalar w, SkScalar h) override { fSize = {w, h}; }
179
draw(SkCanvas * canvas)180 void draw(SkCanvas* canvas) override {
181 SkAutoCanvasRestore acr(canvas, true);
182
183 SkPaint p;
184 p.setAntiAlias(true);
185 p.setColor(SK_ColorWHITE);
186
187 static constexpr float kMeshFraction = 0.85f;
188 const float mesh_size = std::min(fSize.fWidth, fSize.fHeight)*kMeshFraction;
189
190 canvas->translate((fSize.fWidth - mesh_size) * 0.5f,
191 (fSize.fHeight - mesh_size) * 0.5f);
192 canvas->scale(mesh_size, mesh_size);
193
194 auto verts = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode,
195 fVertices.size(),
196 fVertices.data(),
197 fShader ? fUVs.data() : nullptr,
198 fShader ? nullptr : fColors.data(),
199 fIndices.size(),
200 fIndices.data());
201 p.setShader(fShader);
202 canvas->drawVertices(verts, SkBlendMode::kModulate, p);
203
204 if (fShowMesh) {
205 p.setShader(nullptr);
206 p.setColor(SK_ColorBLUE);
207 p.setStroke(true);
208 p.setStrokeWidth(0.5f/mesh_size);
209
210 SkASSERT(fIndices.size() % 6 == 0);
211 for (auto i = fIndices.cbegin(); i < fIndices.cend(); i += 6) {
212 canvas->drawLine(fVertices[i[0]], fVertices[i[1]], p);
213 canvas->drawLine(fVertices[i[1]], fVertices[i[2]], p);
214 canvas->drawLine(fVertices[i[2]], fVertices[i[0]], p);
215 canvas->drawLine(fVertices[i[3]], fVertices[i[4]], p);
216 canvas->drawLine(fVertices[i[4]], fVertices[i[5]], p);
217 canvas->drawLine(fVertices[i[5]], fVertices[i[3]], p);
218 }
219
220 p.setStrokeCap(SkPaint::kRound_Cap);
221 p.setStrokeWidth(5/mesh_size);
222 canvas->drawPoints(SkCanvas::kPoints_PointMode, fVertices.size(), fVertices.data(), p);
223 }
224
225 this->drawControls();
226 }
227
animate(double nanos)228 bool animate(double nanos) override {
229 if (!fTimeBase) {
230 fTimeBase = nanos;
231 }
232
233 // Oscillating between 0..1
234 const float t =
235 std::abs((std::fmod((nanos - fTimeBase)*0.000000001*fAnimationSpeed, 2) - 1));
236
237 // Add some easing
238 fCurrentAnimator->fAanimate(fUVs, fTimeMapper.computeYFromX(t), fVertices);
239
240 return true;
241 }
242
243 private:
initMesh(size_t vertex_count)244 void initMesh(size_t vertex_count) {
245 // Generate an NxN mesh. For simplicity, we keep the vertices in normalized space
246 // (1x1 same as UVs), and scale the mesh up when rendering.
247 const auto n = static_cast<size_t>(std::sqrt(vertex_count));
248 SkASSERT(n > 0);
249 SkASSERT(n == std::sqrt(vertex_count));
250
251 fVertices.resize(vertex_count);
252 fUVs.resize(vertex_count);
253 fColors.resize(vertex_count);
254 for (size_t i = 0; i < vertex_count; ++i) {
255 fVertices[i] = fUVs[i] = {
256 static_cast<float>(i % n) / (n - 1),
257 static_cast<float>(i / n) / (n - 1),
258 };
259 fColors[i] = SkColorSetRGB(!!(i%2)*255,
260 !!(i%3)*255,
261 !!((i+1)%3)*255);
262 }
263
264 // Trivial triangle tessellation pattern:
265 //
266 // *---*---*
267 // | /|\ |
268 // | / | \ |
269 // |/ | \|
270 // *---*---*
271 // |\ | /|
272 // | \ | / |
273 // | \|/ |
274 // *---*---*
275 const size_t triangle_count = 2*(n - 1)*(n - 1),
276 index_count = 3*triangle_count;
277
278 fIndices.clear();
279 fIndices.reserve(index_count);
280 for (size_t i = 0; i < n - 1; ++i) {
281 for (size_t j = 0; j < n - 1; ++j) {
282 const auto row_0_idx = j*n + i,
283 row_1_idx = row_0_idx + n,
284 off_0 = (i + j) % 2,
285 off_1 = 1 - off_0;
286
287 fIndices.push_back(row_0_idx + 0);
288 fIndices.push_back(row_0_idx + 1);
289 fIndices.push_back(row_1_idx + off_0);
290
291 fIndices.push_back(row_0_idx + off_1);
292 fIndices.push_back(row_1_idx + 1);
293 fIndices.push_back(row_1_idx + 0);
294 }
295 }
296
297 SkASSERT(fIndices.size() == index_count);
298 }
299
initShader(const ShaderFactory & fact)300 void initShader(const ShaderFactory& fact) {
301 fShader = fact.fBuild();
302 fCurrentShaderFactory = &fact;
303 }
304
drawControls()305 void drawControls() {
306 ImGui::Begin("Mesh Options");
307
308 if (ImGui::BeginCombo("Texture", fCurrentShaderFactory->fName)) {
309 for (const auto& fact : gShaderFactories) {
310 const auto is_selected = (fCurrentShaderFactory->fName == fact.fName);
311 if (ImGui::Selectable(fact.fName) && !is_selected) {
312 this->initShader(fact);
313 }
314 if (is_selected) {
315 ImGui::SetItemDefaultFocus();
316 }
317 }
318 ImGui::EndCombo();
319 }
320
321 if (ImGui::BeginCombo("Animator", fCurrentAnimator->fName)) {
322 for (const auto& anim : gVertexAnimators) {
323 const auto is_selected = (fCurrentAnimator->fName == anim.fName);
324 if (ImGui::Selectable(anim.fName) && !is_selected) {
325 fCurrentAnimator = &anim;
326 fTimeBase = 0;
327 }
328 if (is_selected) {
329 ImGui::SetItemDefaultFocus();
330 }
331 }
332 ImGui::EndCombo();
333 }
334
335 static constexpr struct {
336 const char* fLabel;
337 size_t fCount;
338 } gSizeInfo[] = {
339 { "4x4", 16 },
340 { "8x8", 64 },
341 { "16x16", 256 },
342 { "32x32", 1024 },
343 { "64x64", 4096 },
344 { "128x128", 16384 },
345 };
346 ImGui::SliderInt("Mesh Size",
347 &fMeshSizeSelector,
348 0, std::size(gSizeInfo) - 1,
349 gSizeInfo[fMeshSizeSelector].fLabel);
350 if (fVertices.size() != gSizeInfo[fMeshSizeSelector].fCount) {
351 this->initMesh(gSizeInfo[fMeshSizeSelector].fCount);
352 }
353
354 ImGui::SliderFloat("Speed", &fAnimationSpeed, 0.25, 4, "%.2f");
355
356 ImGui::Checkbox("Show mesh", &fShowMesh);
357
358 ImGui::End();
359 }
360
361 SkSize fSize;
362 sk_sp<SkShader> fShader;
363 std::vector<SkPoint> fVertices,
364 fUVs;
365 std::vector<SkColor> fColors;
366 std::vector<uint16_t> fIndices;
367
368 double fTimeBase = 0;
369 const SkCubicMap fTimeMapper;
370
371 // UI stuff
372 const ShaderFactory* fCurrentShaderFactory = &gShaderFactories[0];
373 const VertexAnimator* fCurrentAnimator = &gVertexAnimators[0];
374 int fMeshSizeSelector = 2;
375 float fAnimationSpeed = 1.f;
376 bool fShowMesh = false;
377 };
378
379 } // anonymous namespace
380
381 DEF_SLIDE(return new MeshSlide{};)
382