| #include "rive/artboard.hpp" | |
| #include "rive/assets/image_asset.hpp" | |
| #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/math/n_slicer_helpers.hpp" | |
| #include "rive/shapes/image.hpp" | |
| #include "rive/shapes/slice_mesh.hpp" | |
| using namespace rive; | |
| const Corner SliceMesh::patchCorners[] = { | |
| {0, 0}, | |
| {1, 0}, | |
| {1, 1}, | |
| {0, 1}, | |
| }; | |
| const uint16_t SliceMesh::triangulation[] = {0, 1, 3, 1, 2, 3}; | |
| SliceMesh::SliceMesh(NSlicer* nslicer) : m_nslicer(nslicer) {} | |
| void SliceMesh::draw(Renderer* renderer, | |
| const RenderImage* renderImage, | |
| ImageSampler ImageSampler, | |
| BlendMode blendMode, | |
| float opacity) | |
| { | |
| if (m_nslicer == nullptr || m_nslicer->image() == nullptr) | |
| { | |
| return; | |
| } | |
| if (!m_VertexRenderBuffer || !m_UVRenderBuffer || !m_IndexRenderBuffer) | |
| { | |
| return; | |
| } | |
| Image* image = m_nslicer->image(); | |
| renderer->transform(image->worldTransform()); | |
| renderer->translate(-image->width() * image->originX(), | |
| -image->height() * image->originY()); | |
| renderer->drawImageMesh(renderImage, | |
| ImageSampler, | |
| m_VertexRenderBuffer, | |
| m_UVRenderBuffer, | |
| m_IndexRenderBuffer, | |
| static_cast<uint32_t>(m_vertices.size()), | |
| static_cast<uint32_t>(m_indices.size()), | |
| blendMode, | |
| opacity); | |
| } | |
| void SliceMesh::onAssetLoaded(RenderImage* renderImage) {} | |
| void SliceMesh::updateBuffers() | |
| { | |
| auto factory = m_nslicer->artboard()->factory(); | |
| // 1. vertex render buffer | |
| size_t vertexSizeInBytes = m_vertices.size() * sizeof(Vec2D); | |
| if (m_VertexRenderBuffer != nullptr && | |
| m_VertexRenderBuffer->sizeInBytes() != vertexSizeInBytes) | |
| { | |
| m_VertexRenderBuffer = nullptr; | |
| } | |
| if (m_VertexRenderBuffer == nullptr && vertexSizeInBytes != 0) | |
| { | |
| m_VertexRenderBuffer = | |
| factory->makeRenderBuffer(RenderBufferType::vertex, | |
| RenderBufferFlags::none, // optimization? | |
| vertexSizeInBytes); | |
| } | |
| if (m_VertexRenderBuffer != nullptr) | |
| { | |
| Vec2D* mappedVertices = | |
| reinterpret_cast<Vec2D*>(m_VertexRenderBuffer->map()); | |
| if (mappedVertices != nullptr) | |
| { | |
| for (auto v : m_vertices) | |
| { | |
| *mappedVertices++ = v; | |
| } | |
| m_VertexRenderBuffer->unmap(); | |
| } | |
| } | |
| // 2. uv render buffer | |
| size_t uvSizeInBytes = m_uvs.size() * sizeof(Vec2D); | |
| if (m_UVRenderBuffer != nullptr && | |
| m_UVRenderBuffer->sizeInBytes() != uvSizeInBytes) | |
| { | |
| m_UVRenderBuffer = nullptr; | |
| } | |
| if (m_UVRenderBuffer == nullptr && uvSizeInBytes != 0) | |
| { | |
| m_UVRenderBuffer = factory->makeRenderBuffer(RenderBufferType::vertex, | |
| RenderBufferFlags::none, | |
| uvSizeInBytes); | |
| } | |
| if (m_UVRenderBuffer) | |
| { | |
| auto renderImage = m_nslicer->image()->imageAsset()->renderImage(); | |
| Mat2D uvTransform = | |
| renderImage != nullptr ? renderImage->uvTransform() : Mat2D(); | |
| Vec2D* mappedUVs = reinterpret_cast<Vec2D*>(m_UVRenderBuffer->map()); | |
| if (mappedUVs != nullptr) | |
| { | |
| for (auto uv : m_uvs) | |
| { | |
| Vec2D xformedUV = uvTransform * uv; | |
| *mappedUVs++ = xformedUV; | |
| } | |
| m_UVRenderBuffer->unmap(); | |
| } | |
| } | |
| // 3. index render buffer | |
| size_t indexSizeInBytes = m_indices.size() * sizeof(uint16_t); | |
| if (m_IndexRenderBuffer != nullptr && | |
| m_IndexRenderBuffer->sizeInBytes() != indexSizeInBytes) | |
| { | |
| m_IndexRenderBuffer = nullptr; | |
| } | |
| if (m_IndexRenderBuffer == nullptr && indexSizeInBytes != 0) | |
| { | |
| m_IndexRenderBuffer = factory->makeRenderBuffer(RenderBufferType::index, | |
| RenderBufferFlags::none, | |
| indexSizeInBytes); | |
| } | |
| if (m_IndexRenderBuffer) | |
| { | |
| void* mappedIndex = m_IndexRenderBuffer->map(); | |
| if (mappedIndex != nullptr) | |
| { | |
| memcpy(mappedIndex, m_indices.data(), indexSizeInBytes); | |
| m_IndexRenderBuffer->unmap(); | |
| } | |
| } | |
| } | |
| std::vector<float> SliceMesh::uvStops(AxisType forAxis) | |
| { | |
| float imageSize = forAxis == AxisType::X ? m_nslicer->image()->width() | |
| : m_nslicer->image()->height(); | |
| float imageScale = | |
| std::abs(forAxis == AxisType::X ? m_nslicer->image()->renderScaleX() | |
| : m_nslicer->image()->renderScaleY()); | |
| if (imageSize == 0 || imageScale == 0) | |
| { | |
| return {}; | |
| } | |
| const std::vector<Axis*>& axes = | |
| (forAxis == AxisType::X) ? m_nslicer->xs() : m_nslicer->ys(); | |
| return NSlicerHelpers::uvStops(axes, imageSize); | |
| } | |
| std::vector<float> SliceMesh::vertexStops( | |
| const std::vector<float>& normalizedStops, | |
| AxisType forAxis) | |
| { | |
| Image* image = m_nslicer->image(); | |
| float imageSize = forAxis == AxisType::X ? image->width() : image->height(); | |
| // When doing calculations, we assume scale is always non-negative to keep | |
| // everything in image space. | |
| float imageScale = std::abs(forAxis == AxisType::X ? image->renderScaleX() | |
| : image->renderScaleY()); | |
| if (imageSize == 0 || imageScale == 0) | |
| { | |
| return {}; | |
| } | |
| ScaleInfo scaleInfo = | |
| NSlicerHelpers::analyzeUVStops(normalizedStops, imageSize, imageScale); | |
| std::vector<float> vertices; | |
| float vertex = 0.0; | |
| float vertexInBounds = 0.0; | |
| for (int i = 0; i < (int)normalizedStops.size() - 1; i++) | |
| { | |
| vertices.emplace_back(vertexInBounds); | |
| float segment = imageSize * | |
| (normalizedStops[i + 1] - normalizedStops[i]) / | |
| imageScale; | |
| if (NSlicerHelpers::isFixedSegment(i)) | |
| { | |
| vertex += segment; | |
| } | |
| else | |
| { | |
| if (scaleInfo.useScale) | |
| { | |
| vertex += segment * scaleInfo.scaleFactor; | |
| } | |
| else | |
| { | |
| vertex += scaleInfo.fallbackSize; | |
| } | |
| } | |
| vertexInBounds = math::clamp(vertex, 0, imageSize); | |
| } | |
| vertices.emplace_back(vertexInBounds); | |
| return vertices; | |
| } | |
| uint16_t SliceMesh::tileRepeat(std::vector<SliceMeshVertex>& vertices, | |
| std::vector<uint16_t>& indices, | |
| const std::vector<SliceMeshVertex>& 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(); | |
| float scaleX = std::abs(image->renderScaleX()); | |
| float scaleY = std::abs(image->renderScaleY()); | |
| if (scaleX == 0 || scaleY == 0) | |
| { | |
| return 0; | |
| } | |
| const float sizeX = image->width() * (endU - startU) / scaleX; | |
| const float sizeY = image->height() * (endV - startV) / scaleY; | |
| if (std::abs(sizeX) < 1 || std::abs(sizeY) < 1) | |
| { | |
| return 0; | |
| } | |
| float curX = startX; | |
| float curY = startY; | |
| int curV = start; | |
| int escape = 10000; | |
| 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<SliceMeshVertex> 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; | |
| } | |
| return curV - start; | |
| } | |
| void SliceMesh::calc() | |
| { | |
| m_vertices = {}; | |
| m_indices = {}; | |
| m_uvs = {}; | |
| std::vector<float> us = uvStops(AxisType::X); | |
| std::vector<float> vs = uvStops(AxisType::Y); | |
| std::vector<float> xs = vertexStops(us, AxisType::X); | |
| std::vector<float> ys = vertexStops(vs, AxisType::Y); | |
| const auto& tileModes = m_nslicer->tileModes(); | |
| std::vector<SliceMeshVertex> vertices; | |
| uint16_t vertexIndex = 0; | |
| for (int patchY = 0; patchY < (int)vs.size() - 1; patchY++) | |
| { | |
| for (int patchX = 0; patchX < (int)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<SliceMeshVertex> patchVertices; | |
| for (const Corner& corner : patchCorners) | |
| { | |
| int xIndex = patchX + corner.x; | |
| int yIndex = patchY + corner.y; | |
| SliceMeshVertex v; | |
| 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); | |
| } | |
| if (tileMode == NSlicerTileModeType::REPEAT) | |
| { | |
| vertexIndex += | |
| tileRepeat(vertices, m_indices, patchVertices, v0); | |
| } | |
| else | |
| { | |
| vertices.insert(vertices.end(), | |
| patchVertices.begin(), | |
| patchVertices.end()); | |
| for (uint16_t t : triangulation) | |
| { | |
| m_indices.emplace_back(v0 + t); | |
| } | |
| } | |
| } | |
| } | |
| for (const SliceMeshVertex& v : vertices) | |
| { | |
| m_vertices.emplace_back(v.vertex); | |
| m_uvs.emplace_back(v.uv); | |
| } | |
| } | |
| void SliceMesh::update() | |
| { | |
| // Make sure the image is loaded | |
| if (m_nslicer == nullptr || m_nslicer->image() == nullptr || | |
| m_nslicer->image()->imageAsset() == nullptr) | |
| { | |
| return; | |
| } | |
| calc(); | |
| updateBuffers(); | |
| } |