blob: ee61ab1d55e4b8821114d8e946ed2b011054c23d [file]
#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/hit_test.hpp"
#include "rive/math/math_types.hpp"
#include "rive/math/n_slicer_helpers.hpp"
#include "rive/shapes/mesh_host.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,
float additiveness)
{
MeshHost* host = m_nslicer != nullptr ? m_nslicer->host() : nullptr;
if (host == nullptr)
{
return;
}
if (!m_VertexRenderBuffer || !m_UVRenderBuffer || !m_IndexRenderBuffer)
{
return;
}
const Vec2D origin = host->meshHostOrigin();
renderer->transform(host->meshHostWorldTransform());
renderer->translate(
-host->meshHostWidth() * origin.x,
-host->meshHostHeight() * origin.y);
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,
additiveness);
}
// What draw() renders the vertices under; false without a host.
bool SliceMesh::drawTransform(Mat2D* transform) const
{
MeshHost* host = m_nslicer != nullptr ? m_nslicer->host() : nullptr;
if (host == nullptr)
{
return false;
}
const Vec2D origin = host->meshHostOrigin();
*transform = host->meshHostWorldTransform() *
Mat2D::fromTranslate(
-host->meshHostWidth() * origin.x,
-host->meshHostHeight() * origin.y);
return true;
}
bool SliceMesh::hitTest(Vec2D position)
{
// Hidden patches have no triangles, so they stay holes here too.
Mat2D world;
Mat2D inverseWorld;
if (!drawTransform(&world) || !world.invert(&inverseWorld))
{
return false;
}
return HitTester::testMesh(inverseWorld * position, m_vertices, m_indices);
}
bool SliceMesh::uvAt(Vec2D position, Vec2D* uv, bool* inside, Mat2D* toUV)
{
Mat2D world;
Mat2D inverseWorld;
if (!drawTransform(&world) || !world.invert(&inverseWorld) ||
!HitTester::mapMesh(
inverseWorld * position,
m_vertices,
m_uvs,
m_indices,
uv,
inside,
toUV))
{
return false;
}
if (toUV != nullptr)
{
*toUV = *toUV * inverseWorld;
}
return true;
}
bool SliceMesh::positionAtUV(Vec2D uv, Vec2D* position)
{
Mat2D world;
Vec2D local;
bool inside;
if (!drawTransform(&world) ||
!HitTester::mapMesh(uv, m_uvs, m_vertices, m_indices, &local, &inside))
{
return false;
}
*position = world * local;
return true;
}
void SliceMesh::onAssetLoaded(RenderImage* renderImage) {}
void SliceMesh::uvTransformChanged(const Mat2D& uvTransform) { update(); }
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)
{
Mat2D uvTransform = m_nslicer->host()->meshHostUVTransform();
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)
{
MeshHost* host = m_nslicer->host();
float imageSize = forAxis == AxisType::X ? host->meshHostWidth()
: host->meshHostHeight();
float imageScale = std::abs(
forAxis == AxisType::X ? host->meshHostScaleX() : host->meshHostScaleY());
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)
{
MeshHost* host = m_nslicer->host();
float imageSize = forAxis == AxisType::X ? host->meshHostWidth()
: host->meshHostHeight();
// When doing calculations, we assume scale is always non-negative to keep
// everything in image space.
float imageScale = std::abs(
forAxis == AxisType::X ? host->meshHostScaleX() : host->meshHostScaleY());
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
MeshHost* host = m_nslicer->host();
float scaleX = std::abs(host->meshHostScaleX());
float scaleY = std::abs(host->meshHostScaleY());
if (scaleX == 0 || scaleY == 0)
{
return 0;
}
const float sizeX = host->meshHostWidth() * (endU - startU) / scaleX;
const float sizeY = host->meshHostHeight() * (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()
{
MeshHost* host = m_nslicer != nullptr ? m_nslicer->host() : nullptr;
if (host == nullptr || !host->meshHostReady())
{
return;
}
calc();
updateBuffers();
}