| /* |
| * Copyright 2023 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "include/core/SkBlendMode.h" |
| #include "include/core/SkCanvas.h" |
| #include "include/core/SkColor.h" |
| #include "include/core/SkCubicMap.h" |
| #include "include/core/SkImage.h" |
| #include "include/core/SkPoint.h" |
| #include "include/core/SkSamplingOptions.h" |
| #include "include/core/SkScalar.h" |
| #include "include/core/SkShader.h" |
| #include "include/core/SkTileMode.h" |
| #include "include/core/SkVertices.h" |
| #include "include/effects/SkGradientShader.h" |
| #include "include/private/base/SkFloatingPoint.h" |
| #include "tools/DecodeUtils.h" |
| #include "tools/Resources.h" |
| #include "tools/timer/TimeUtils.h" |
| #include "tools/viewer/Slide.h" |
| |
| #include <cmath> |
| #include <cstddef> |
| #include <cstdint> |
| #include <iterator> |
| #include <vector> |
| |
| #include "imgui.h" |
| |
| namespace { |
| |
| float lerp(float a, float b, float t) { |
| return a + (b - a)*t; |
| } |
| |
| sk_sp<SkShader> make_image_shader(const char* resource) { |
| sk_sp<SkImage> img = ToolUtils::GetResourceAsImage(resource); |
| |
| // Normalize to 1x1 for UV sampling. |
| const auto lm = SkMatrix::Scale(1.0f/img->width(), 1.0f/img->height()); |
| |
| return img->makeShader(SkTileMode::kDecal, |
| SkTileMode::kDecal, |
| SkSamplingOptions(SkFilterMode::kLinear), |
| &lm); |
| } |
| |
| static constexpr struct ShaderFactory { |
| const char* fName; |
| sk_sp<SkShader> (*fBuild)(); |
| } gShaderFactories[] = { |
| { |
| "Img (Mandrill)", |
| []() ->sk_sp<SkShader> { return make_image_shader("images/mandrill_512.png"); } |
| }, |
| { |
| "Img (Baby Tux)", |
| []() ->sk_sp<SkShader> { return make_image_shader("images/baby_tux.png"); } |
| }, |
| { |
| "Img (Brickwork)", |
| []() ->sk_sp<SkShader> { return make_image_shader("images/brickwork-texture.jpg"); } |
| }, |
| { |
| "Radial Gradient", |
| []() ->sk_sp<SkShader> { |
| static constexpr SkColor gColors[] = { |
| SK_ColorGREEN, SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN |
| }; |
| return SkGradientShader::MakeRadial({0.5f, 0.5f}, 0.5f, gColors, nullptr, |
| std::size(gColors), SkTileMode::kRepeat); |
| } |
| }, |
| { |
| "Colors", |
| []() ->sk_sp<SkShader> { return nullptr; } |
| }, |
| }; |
| |
| static constexpr struct VertexAnimator { |
| const char* fName; |
| void (*fAanimate)(const std::vector<SkPoint>& uv, float t, std::vector<SkPoint>& out); |
| } gVertexAnimators[] = { |
| { |
| "Cylinderator", |
| // Simulate a cylinder rolling sideways across the 1x1 uv space. |
| [](const std::vector<SkPoint>& uvs, float t, std::vector<SkPoint>& out) { |
| static constexpr float kCylRadius = .2f; |
| |
| const auto cyl_pos = t; |
| |
| for (size_t i = 0; i < uvs.size(); ++i) { |
| const auto& uv = uvs[i]; |
| |
| if (uv.fX <= cyl_pos) { |
| out[i] = uv; |
| continue; |
| } |
| |
| const auto arc_len = uv.fX - cyl_pos, |
| arc_ang = arc_len/kCylRadius; |
| |
| out[i] = SkPoint{ |
| cyl_pos + std::sin(arc_ang)*kCylRadius, |
| uv.fY, |
| }; |
| } |
| }, |
| }, |
| { |
| "Squircillator", |
| // Pull all vertices towards center, proportionally, such that the outer square edge |
| // is mapped to a circle for t == 1. |
| [](const std::vector<SkPoint>& uvs, float t, std::vector<SkPoint>& out) { |
| for (size_t i = 0; i < uvs.size(); ++i) { |
| // remap to [-.5,.5] |
| const auto uv = (uvs[i] - SkPoint{0.5,0.5}); |
| |
| // Distance from center to outer edge for the line pasing through uv. |
| const auto d = uv.length()*0.5f/std::max(std::abs(uv.fX), std::abs(uv.fY)); |
| // Scale needed to pull the outer edge to the r=0.5 circle at t == 1. |
| const auto s = lerp(1, (0.5f / d), t); |
| |
| out[i] = uv*s + SkPoint{0.5, 0.5}; |
| } |
| }, |
| }, |
| { |
| "Twirlinator", |
| // Rotate vertices proportional to their distance to center. |
| [](const std::vector<SkPoint>& uvs, float t, std::vector<SkPoint>& out) { |
| static constexpr float kMaxRotate = SK_FloatPI*4; |
| |
| for (size_t i = 0; i < uvs.size(); ++i) { |
| // remap to [-.5,.5] |
| const auto uv = (uvs[i] - SkPoint{0.5,0.5}); |
| const auto angle = kMaxRotate * t * uv.length(); |
| |
| out[i] = SkMatrix::RotateRad(angle).mapPoint(uv) + SkPoint{0.5, 0.5}; |
| } |
| }, |
| }, |
| { |
| "Wigglynator", |
| [](const std::vector<SkPoint>& uvs, float t, std::vector<SkPoint>& out) { |
| const float radius = t*0.2f/(std::sqrt(uvs.size()) - 1); |
| for (size_t i = 0; i < uvs.size(); ++i) { |
| const float phase = i*SK_FloatPI*0.31f, |
| angle = phase + t*SK_FloatPI*2; |
| out[i] = uvs[i] + SkVector{ |
| radius*std::cos(angle), |
| radius*std::sin(angle), |
| }; |
| } |
| }, |
| }, |
| { |
| "None", |
| [](const std::vector<SkPoint>& uvs, float, std::vector<SkPoint>& out) { out = uvs; }, |
| }, |
| }; |
| |
| class MeshSlide final : public Slide { |
| public: |
| MeshSlide() : fTimeMapper({0.5f, 0}, {0.5f, 1}) { fName = "Mesh"; } |
| |
| void load(SkScalar w, SkScalar h) override { |
| fSize = {w, h}; |
| |
| this->initMesh(256); |
| this->initShader(gShaderFactories[0]); |
| } |
| |
| void resize(SkScalar w, SkScalar h) override { fSize = {w, h}; } |
| |
| void draw(SkCanvas* canvas) override { |
| SkAutoCanvasRestore acr(canvas, true); |
| |
| SkPaint p; |
| p.setAntiAlias(true); |
| p.setColor(SK_ColorWHITE); |
| |
| static constexpr float kMeshFraction = 0.85f; |
| const float mesh_size = std::min(fSize.fWidth, fSize.fHeight)*kMeshFraction; |
| |
| canvas->translate((fSize.fWidth - mesh_size) * 0.5f, |
| (fSize.fHeight - mesh_size) * 0.5f); |
| canvas->scale(mesh_size, mesh_size); |
| |
| auto verts = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode, |
| fVertices.size(), |
| fVertices.data(), |
| fShader ? fUVs.data() : nullptr, |
| fShader ? nullptr : fColors.data(), |
| fIndices.size(), |
| fIndices.data()); |
| p.setShader(fShader); |
| canvas->drawVertices(verts, SkBlendMode::kModulate, p); |
| |
| if (fShowMesh) { |
| p.setShader(nullptr); |
| p.setColor(SK_ColorBLUE); |
| p.setStroke(true); |
| p.setStrokeWidth(0.5f/mesh_size); |
| |
| SkASSERT(fIndices.size() % 6 == 0); |
| for (auto i = fIndices.cbegin(); i < fIndices.cend(); i += 6) { |
| canvas->drawLine(fVertices[i[0]], fVertices[i[1]], p); |
| canvas->drawLine(fVertices[i[1]], fVertices[i[2]], p); |
| canvas->drawLine(fVertices[i[2]], fVertices[i[0]], p); |
| canvas->drawLine(fVertices[i[3]], fVertices[i[4]], p); |
| canvas->drawLine(fVertices[i[4]], fVertices[i[5]], p); |
| canvas->drawLine(fVertices[i[5]], fVertices[i[3]], p); |
| } |
| |
| p.setStrokeCap(SkPaint::kRound_Cap); |
| p.setStrokeWidth(5/mesh_size); |
| canvas->drawPoints(SkCanvas::kPoints_PointMode, fVertices.size(), fVertices.data(), p); |
| } |
| |
| this->drawControls(); |
| } |
| |
| bool animate(double nanos) override { |
| if (!fTimeBase) { |
| fTimeBase = nanos; |
| } |
| |
| // Oscillating between 0..1 |
| const float t = |
| std::abs((std::fmod((nanos - fTimeBase)*0.000000001*fAnimationSpeed, 2) - 1)); |
| |
| // Add some easing |
| fCurrentAnimator->fAanimate(fUVs, fTimeMapper.computeYFromX(t), fVertices); |
| |
| return true; |
| } |
| |
| private: |
| void initMesh(size_t vertex_count) { |
| // Generate an NxN mesh. For simplicity, we keep the vertices in normalized space |
| // (1x1 same as UVs), and scale the mesh up when rendering. |
| const auto n = static_cast<size_t>(std::sqrt(vertex_count)); |
| SkASSERT(n > 0); |
| SkASSERT(n == std::sqrt(vertex_count)); |
| |
| fVertices.resize(vertex_count); |
| fUVs.resize(vertex_count); |
| fColors.resize(vertex_count); |
| for (size_t i = 0; i < vertex_count; ++i) { |
| fVertices[i] = fUVs[i] = { |
| static_cast<float>(i % n) / (n - 1), |
| static_cast<float>(i / n) / (n - 1), |
| }; |
| fColors[i] = SkColorSetRGB(!!(i%2)*255, |
| !!(i%3)*255, |
| !!((i+1)%3)*255); |
| } |
| |
| // Trivial triangle tessellation pattern: |
| // |
| // *---*---* |
| // | /|\ | |
| // | / | \ | |
| // |/ | \| |
| // *---*---* |
| // |\ | /| |
| // | \ | / | |
| // | \|/ | |
| // *---*---* |
| const size_t triangle_count = 2*(n - 1)*(n - 1), |
| index_count = 3*triangle_count; |
| |
| fIndices.clear(); |
| fIndices.reserve(index_count); |
| for (size_t i = 0; i < n - 1; ++i) { |
| for (size_t j = 0; j < n - 1; ++j) { |
| const auto row_0_idx = j*n + i, |
| row_1_idx = row_0_idx + n, |
| off_0 = (i + j) % 2, |
| off_1 = 1 - off_0; |
| |
| fIndices.push_back(row_0_idx + 0); |
| fIndices.push_back(row_0_idx + 1); |
| fIndices.push_back(row_1_idx + off_0); |
| |
| fIndices.push_back(row_0_idx + off_1); |
| fIndices.push_back(row_1_idx + 1); |
| fIndices.push_back(row_1_idx + 0); |
| } |
| } |
| |
| SkASSERT(fIndices.size() == index_count); |
| } |
| |
| void initShader(const ShaderFactory& fact) { |
| fShader = fact.fBuild(); |
| fCurrentShaderFactory = &fact; |
| } |
| |
| void drawControls() { |
| ImGui::Begin("Mesh Options"); |
| |
| if (ImGui::BeginCombo("Texture", fCurrentShaderFactory->fName)) { |
| for (const auto& fact : gShaderFactories) { |
| const auto is_selected = (fCurrentShaderFactory->fName == fact.fName); |
| if (ImGui::Selectable(fact.fName) && !is_selected) { |
| this->initShader(fact); |
| } |
| if (is_selected) { |
| ImGui::SetItemDefaultFocus(); |
| } |
| } |
| ImGui::EndCombo(); |
| } |
| |
| if (ImGui::BeginCombo("Animator", fCurrentAnimator->fName)) { |
| for (const auto& anim : gVertexAnimators) { |
| const auto is_selected = (fCurrentAnimator->fName == anim.fName); |
| if (ImGui::Selectable(anim.fName) && !is_selected) { |
| fCurrentAnimator = &anim; |
| fTimeBase = 0; |
| } |
| if (is_selected) { |
| ImGui::SetItemDefaultFocus(); |
| } |
| } |
| ImGui::EndCombo(); |
| } |
| |
| static constexpr struct { |
| const char* fLabel; |
| size_t fCount; |
| } gSizeInfo[] = { |
| { "4x4", 16 }, |
| { "8x8", 64 }, |
| { "16x16", 256 }, |
| { "32x32", 1024 }, |
| { "64x64", 4096 }, |
| { "128x128", 16384 }, |
| }; |
| ImGui::SliderInt("Mesh Size", |
| &fMeshSizeSelector, |
| 0, std::size(gSizeInfo) - 1, |
| gSizeInfo[fMeshSizeSelector].fLabel); |
| if (fVertices.size() != gSizeInfo[fMeshSizeSelector].fCount) { |
| this->initMesh(gSizeInfo[fMeshSizeSelector].fCount); |
| } |
| |
| ImGui::SliderFloat("Speed", &fAnimationSpeed, 0.25, 4, "%.2f"); |
| |
| ImGui::Checkbox("Show mesh", &fShowMesh); |
| |
| ImGui::End(); |
| } |
| |
| SkSize fSize; |
| sk_sp<SkShader> fShader; |
| std::vector<SkPoint> fVertices, |
| fUVs; |
| std::vector<SkColor> fColors; |
| std::vector<uint16_t> fIndices; |
| |
| double fTimeBase = 0; |
| const SkCubicMap fTimeMapper; |
| |
| // UI stuff |
| const ShaderFactory* fCurrentShaderFactory = &gShaderFactories[0]; |
| const VertexAnimator* fCurrentAnimator = &gVertexAnimators[0]; |
| int fMeshSizeSelector = 2; |
| float fAnimationSpeed = 1.f; |
| bool fShowMesh = false; |
| }; |
| |
| } // anonymous namespace |
| |
| DEF_SLIDE(return new MeshSlide{};) |