From 4babbdbc36bd5f156b258b8828aa76da88798050 Mon Sep 17 00:00:00 2001 From: susan101566 Date: Mon, 26 Aug 2024 18:08:48 +0000 Subject: [PATCH] runtime: add tiling to n-slicing This PR translates the logic for tiling into the n-slicing runtime implementation. It additionally computes patchIndex on editor export. Demo: ![aaaaa](https://github.com/user-attachments/assets/819e437c-f91b-4d83-872c-7e6a0009809d) doc: https://www.notion.so/rive-app/9-Slice-Tech-Proposal-Image-only-50b25ea8e79c4efabb681110e288f064 Diffs= 728ac6286 runtime: add tiling to n-slicing (#7934) Co-authored-by: Susan Wang --- .rive_head | 2 +- include/rive/layout/n_slicer.hpp | 9 +- include/rive/layout/n_slicer_tile_mode.hpp | 7 ++ include/rive/shapes/slice_mesh.hpp | 5 + src/layout/n_slicer.cpp | 12 ++- src/layout/n_slicer_tile_mode.cpp | 2 +- src/shapes/slice_mesh.cpp | 118 ++++++++++++++++++++- 7 files changed, 145 insertions(+), 10 deletions(-) diff --git a/.rive_head b/.rive_head index 7d187dca..39051bfa 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -e1b4a53ecb34d62e9d4160e3040803173c2233b0 +728ac6286912a1bf9987a91488c698668bd67354 diff --git a/include/rive/layout/n_slicer.hpp b/include/rive/layout/n_slicer.hpp index 3be28c2e..d3becdb4 100644 --- a/include/rive/layout/n_slicer.hpp +++ b/include/rive/layout/n_slicer.hpp @@ -2,12 +2,15 @@ #define _RIVE_N_SLICER_HPP_ #include "rive/generated/layout/n_slicer_base.hpp" #include +#include + namespace rive { class Image; class SliceMesh; class Axis; class NSlicerTileMode; +enum class NSlicerTileModeType : int; class NSlicer : public NSlicerBase { @@ -15,7 +18,7 @@ class NSlicer : public NSlicerBase std::unique_ptr m_sliceMesh; // the mesh that gets drawn std::vector m_xs; std::vector m_ys; - std::vector m_tileModes; + std::unordered_map m_tileModes; // patchIndex key public: NSlicer(); @@ -25,12 +28,14 @@ class NSlicer : public NSlicerBase Image* image(); SliceMesh* sliceMesh() { return m_sliceMesh.get(); }; + int patchIndex(int patchX, int patchY); const std::vector& xs() { return m_xs; } const std::vector& ys() { return m_ys; } + const std::unordered_map& tileModes() { return m_tileModes; } void addAxisX(Axis* axis); void addAxisY(Axis* axis); - void addTileMode(NSlicerTileMode* tileMode); + void addTileMode(int patchIndex, NSlicerTileModeType style); void axisChanged(); // only axis gets animated at runtime }; } // namespace rive diff --git a/include/rive/layout/n_slicer_tile_mode.hpp b/include/rive/layout/n_slicer_tile_mode.hpp index 3a2fbffe..9394c689 100644 --- a/include/rive/layout/n_slicer_tile_mode.hpp +++ b/include/rive/layout/n_slicer_tile_mode.hpp @@ -4,6 +4,13 @@ #include namespace rive { +enum class NSlicerTileModeType : int +{ + STRETCH = 0, + REPEAT = 1, + HIDDEN = 2, +}; + class NSlicerTileMode : public NSlicerTileModeBase { public: diff --git a/include/rive/shapes/slice_mesh.hpp b/include/rive/shapes/slice_mesh.hpp index f5259a65..a7477790 100644 --- a/include/rive/shapes/slice_mesh.hpp +++ b/include/rive/shapes/slice_mesh.hpp @@ -35,6 +35,11 @@ class SliceMesh : public MeshDrawable std::vector uvStops(AxisType forAxis); std::vector vertexStops(const std::vector& normalizedStops, AxisType forAxis); + uint16_t tileRepeat(std::vector& vertices, + std::vector& indices, + const std::vector& box, + uint16_t start); + // Update the member (non-render) buffers. void calc(); diff --git a/src/layout/n_slicer.cpp b/src/layout/n_slicer.cpp index 263ddee4..1764ca1f 100644 --- a/src/layout/n_slicer.cpp +++ b/src/layout/n_slicer.cpp @@ -15,7 +15,12 @@ Image* NSlicer::image() return parent()->as(); } return nullptr; -}; +} + +int NSlicer::patchIndex(int patchX, int patchY) +{ + return patchY * (static_cast(m_xs.size()) + 1) + patchX; +} StatusCode NSlicer::onAddedDirty(CoreContext* context) { @@ -46,7 +51,10 @@ void NSlicer::addAxisY(Axis* axis) { m_ys.push_back(axis); } void NSlicer::axisChanged() { addDirt(ComponentDirt::Path); } -void NSlicer::addTileMode(NSlicerTileMode* tileMode) { m_tileModes.push_back(tileMode); } +void NSlicer::addTileMode(int patchIndex, NSlicerTileModeType style) +{ + m_tileModes[patchIndex] = style; +} void NSlicer::update(ComponentDirt value) { diff --git a/src/layout/n_slicer_tile_mode.cpp b/src/layout/n_slicer_tile_mode.cpp index 2ec570d3..d6367c23 100644 --- a/src/layout/n_slicer_tile_mode.cpp +++ b/src/layout/n_slicer_tile_mode.cpp @@ -15,6 +15,6 @@ StatusCode NSlicerTileMode::onAddedDirty(CoreContext* context) { return StatusCode::MissingObject; } - parent()->as()->addTileMode(this); + parent()->as()->addTileMode(patchIndex(), NSlicerTileModeType(style())); return StatusCode::Ok; } diff --git a/src/shapes/slice_mesh.cpp b/src/shapes/slice_mesh.cpp index 8d5d57dd..874f3fa3 100644 --- a/src/shapes/slice_mesh.cpp +++ b/src/shapes/slice_mesh.cpp @@ -3,6 +3,7 @@ #include "rive/factory.hpp" #include "rive/layout/axis.hpp" #include "rive/layout/n_slicer.hpp" +#include "rive/layout/n_slicer_tile_mode.hpp" #include "rive/math/math_types.hpp" #include "rive/shapes/image.hpp" #include "rive/shapes/slice_mesh.hpp" @@ -180,6 +181,93 @@ std::vector SliceMesh::vertexStops(const std::vector& normalizedSt return result; } +uint16_t SliceMesh::tileRepeat(std::vector& vertices, + std::vector& indices, + const std::vector& box, + uint16_t start) +{ + assert(box.size() == 4); + + const float startX = box[0].vertex.x; + const float startY = box[0].vertex.y; + const float endX = box[2].vertex.x; + const float endY = box[2].vertex.y; + + const float startU = box[0].uv.x; + const float startV = box[0].uv.y; + const float endU = box[2].uv.x; + const float endV = box[2].uv.y; + + // The size of each repeated tile in image space + Image* image = m_nslicer->image(); + const float sizeX = image->width() * (endU - startU) / std::abs(image->scaleX()); + const float sizeY = image->height() * (endV - startV) / std::abs(image->scaleY()); + + float curX = startX; + float curY = startY; + int curV = start; + + int escape = 1000000; // a million + while (curY < endY && escape > 0) + { + escape--; + float fracY = (curY + sizeY) > endY ? (endY - curY) / sizeY : 1; + curX = startX; + while (curX < endX && escape > 0) + { + escape--; + int v0 = curV; + float fracX = (curX + sizeX) > endX ? (endX - curX) / sizeX : 1; + + std::vector curTile; + float endU1 = startU + (endU - startU) * fracX; + float endV1 = startV + (endV - startV) * fracY; + float endX1 = curX + sizeX * fracX; + float endY1 = curY + sizeY * fracY; + + // top left + SliceMeshVertex v = SliceMeshVertex(); + v.id = curV++; + v.uv = Vec2D(startU, startV); + v.vertex = Vec2D(curX, curY); + curTile.emplace_back(v); + + // top right + v = SliceMeshVertex(); + v.id = curV++; + v.uv = Vec2D(endU1, startV); + v.vertex = Vec2D(endX1, curY); + curTile.emplace_back(v); + + // bottom right + v = SliceMeshVertex(); + v.id = curV++; + v.uv = Vec2D(endU1, endV1); + v.vertex = Vec2D(endX1, endY1); + curTile.emplace_back(v); + + // bottom left + v = SliceMeshVertex(); + v.id = curV++; + v.uv = Vec2D(startU, endV1); + v.vertex = Vec2D(curX, endY1); + curTile.emplace_back(v); + + // Commit the four vertices, and the triangulation + vertices.insert(vertices.end(), curTile.begin(), curTile.end()); + for (uint16_t t : triangulation) + { + indices.emplace_back(v0 + t); + } + + curX += sizeX; + } + curY += sizeY; + } + assert(escape > 0); + return curV - start; +} + void SliceMesh::calc() { m_vertices = {}; @@ -190,6 +278,7 @@ void SliceMesh::calc() std::vector vs = uvStops(AxisType::Y); std::vector xs = vertexStops(us, AxisType::X); std::vector ys = vertexStops(vs, AxisType::Y); + const auto& tileModes = m_nslicer->tileModes(); std::vector vertices; uint16_t vertexIndex = 0; @@ -197,6 +286,16 @@ void SliceMesh::calc() { for (int patchX = 0; patchX < us.size() - 1; patchX++) { + auto tileModeIt = tileModes.find(m_nslicer->patchIndex(patchX, patchY)); + auto tileMode = + tileModeIt == tileModes.end() ? NSlicerTileModeType::STRETCH : tileModeIt->second; + + // Do nothing if hidden + if (tileMode == NSlicerTileModeType::HIDDEN) + { + continue; + } + const uint16_t v0 = vertexIndex; std::vector patchVertices; for (const Corner& corner : patchCorners) @@ -205,16 +304,27 @@ void SliceMesh::calc() int yIndex = patchY + corner.y; SliceMeshVertex v; - v.id = vertexIndex++; + if (tileMode != NSlicerTileModeType::REPEAT) + { + v.id = vertexIndex++; + } v.uv = Vec2D(us[xIndex], vs[yIndex]); v.vertex = Vec2D(xs[xIndex], ys[yIndex]); patchVertices.emplace_back(v); } - vertices.insert(vertices.end(), patchVertices.begin(), patchVertices.end()); - for (uint16_t t : triangulation) + + if (tileMode == NSlicerTileModeType::REPEAT) + { + vertexIndex += tileRepeat(vertices, m_indices, patchVertices, v0); + } + else { - m_indices.emplace_back(v0 + t); + vertices.insert(vertices.end(), patchVertices.begin(), patchVertices.end()); + for (uint16_t t : triangulation) + { + m_indices.emplace_back(v0 + t); + } } } }