blob: 43c6cd8b03e622532948f2ca152f7831bbd37540 [file]
/*
* Copyright 2022 Rive
*/
#include "rive_render_path.hpp"
#include "rive/math/bezier_utils.hpp"
#include "rive/math/simd.hpp"
#include "rive/math/wangs_formula.hpp"
namespace rive
{
RiveRenderPath::RiveRenderPath(FillRule fillRule, RawPath& rawPath)
{
m_rawPath.swap(rawPath);
m_rawPath.pruneEmptySegments();
}
void RiveRenderPath::rewind()
{
assert(m_rawPathMutationLockCount == 0);
m_rawPath.rewind();
m_dirt = kAllDirt;
}
void RiveRenderPath::moveTo(float x, float y)
{
assert(m_rawPathMutationLockCount == 0);
m_rawPath.moveTo(x, y);
m_dirt = kAllDirt;
}
void RiveRenderPath::lineTo(float x, float y)
{
assert(m_rawPathMutationLockCount == 0);
// Make sure to start a new contour, even if this line is empty.
m_rawPath.injectImplicitMoveIfNeeded();
Vec2D p1 = {x, y};
if (m_rawPath.points().back() != p1)
{
m_rawPath.line(p1);
}
m_dirt = kAllDirt;
}
void RiveRenderPath::cubicTo(float ox,
float oy,
float ix,
float iy,
float x,
float y)
{
assert(m_rawPathMutationLockCount == 0);
// Make sure to start a new contour, even if this cubic is empty.
m_rawPath.injectImplicitMoveIfNeeded();
Vec2D p1 = {ox, oy};
Vec2D p2 = {ix, iy};
Vec2D p3 = {x, y};
if (m_rawPath.points().back() != p1 || p1 != p2 || p2 != p3)
{
m_rawPath.cubic(p1, p2, p3);
}
m_dirt = kAllDirt;
}
void RiveRenderPath::close()
{
assert(m_rawPathMutationLockCount == 0);
m_rawPath.close();
m_dirt = kAllDirt;
}
void RiveRenderPath::addRenderPath(RenderPath* path, const Mat2D& matrix)
{
assert(m_rawPathMutationLockCount == 0);
RiveRenderPath* riveRenderPath = static_cast<RiveRenderPath*>(path);
RawPath::Iter transformedPathIter =
m_rawPath.addPath(riveRenderPath->m_rawPath, &matrix);
if (matrix != Mat2D())
{
// Prune any segments that became empty after the transform.
m_rawPath.pruneEmptySegments(transformedPathIter);
}
m_dirt = kAllDirt;
}
const AABB& RiveRenderPath::getBounds() const
{
if (m_dirt & kPathBoundsDirt)
{
m_bounds = m_rawPath.bounds();
m_dirt &= ~kPathBoundsDirt;
}
return m_bounds;
}
float RiveRenderPath::getCoarseArea() const
{
if (m_dirt & kPathCoarseAreaDirt)
{
float a = 0;
Vec2D contourP0 = {0, 0}, lastPt = {0, 0};
for (auto [verb, pts] : m_rawPath)
{
switch (verb)
{
case PathVerb::move:
a += Vec2D::cross(lastPt, contourP0);
contourP0 = lastPt = pts[0];
break;
case PathVerb::close:
break;
case PathVerb::line:
a += Vec2D::cross(lastPt, pts[1]);
lastPt = pts[1];
break;
case PathVerb::quad:
RIVE_UNREACHABLE();
case PathVerb::cubic:
{
// Linearize the cubic in artboard space, then add up the
// area for each segment.
float n = ceilf(
wangs_formula::cubic(pts, 1.f / kCoarseAreaTolerance));
if (n > 1)
{
n = std::min(n, 64.f);
float4 t = float4{1, 1, 2, 2} * (1 / n);
float4 dt = t.w;
math::EvalCubic evalCubic(pts);
for (; t.x < 1; t += dt)
{
float4 p = evalCubic(t);
Vec2D lo = {p.x, p.y};
a += Vec2D::cross(lastPt, lo);
lastPt = lo;
if (t.y < 1)
{
Vec2D hi = {p.z, p.w};
a += Vec2D::cross(lastPt, hi);
lastPt = hi;
}
}
}
a += Vec2D::cross(lastPt, pts[3]);
lastPt = pts[3];
break;
}
}
}
a += Vec2D::cross(lastPt, contourP0);
m_coarseArea = a * .5f;
m_dirt &= ~kPathCoarseAreaDirt;
}
return m_coarseArea;
}
bool RiveRenderPath::isClockwiseDominant(const Mat2D& viewMatrix) const
{
float matrixDeterminant =
viewMatrix[0] * viewMatrix[3] - viewMatrix[2] * viewMatrix[1];
return getCoarseArea() * matrixDeterminant >= 0;
}
uint64_t RiveRenderPath::getRawPathMutationID() const
{
static std::atomic<uint64_t> uniqueIDCounter = 0;
if (m_dirt & kRawPathMutationIDDirt)
{
m_rawPathMutationID = ++uniqueIDCounter;
m_dirt &= ~kRawPathMutationIDDirt;
}
return m_rawPathMutationID;
}
void RiveRenderPath::setDrawCache(gpu::RiveRenderPathDraw* drawCache,
const Mat2D& mat,
rive::RiveRenderPaint* riveRenderPaint) const
{
CacheElements& cache =
m_cachedElements[riveRenderPaint->getIsStroked() ? CACHE_STROKED
: CACHE_FILLED];
cache.draw = drawCache;
cache.xx = mat.xx();
cache.xy = mat.xy();
cache.yx = mat.yx();
cache.yy = mat.yy();
if (riveRenderPaint->getIsStroked())
{
m_cachedThickness = riveRenderPaint->getThickness();
m_cachedJoin = riveRenderPaint->getJoin();
m_cachedCap = riveRenderPaint->getCap();
}
}
gpu::DrawUniquePtr RiveRenderPath::getDrawCache(
const Mat2D& matrix,
const RiveRenderPaint* paint,
FillRule fillRule,
TrivialBlockAllocator* allocator,
const gpu::RenderContext::FrameDescriptor& frameDesc,
gpu::InterlockMode interlockMode) const
{
const CacheElements& cache =
m_cachedElements[paint->getIsStroked() ? CACHE_STROKED : CACHE_FILLED];
if (cache.draw == nullptr)
{
return nullptr;
}
if (paint->getIsStroked())
{
if (m_cachedThickness != paint->getThickness())
{
return nullptr;
}
if (m_cachedJoin != paint->getJoin())
{
return nullptr;
}
if (m_cachedCap != paint->getCap())
{
return nullptr;
}
}
if (matrix.xx() != cache.xx || matrix.xy() != cache.xy ||
matrix.yx() != cache.yx || matrix.yy() != cache.yy)
{
return nullptr;
}
return gpu::DrawUniquePtr(
allocator->make<gpu::RiveRenderPathDraw>(*cache.draw,
matrix.tx(),
matrix.ty(),
ref_rcp(this),
fillRule,
paint,
frameDesc,
interlockMode));
}
} // namespace rive