blob: a5ae9a02cf3aa76821896c338f3033f2286cddcf [file] [log] [blame]
/*
* Copyright 2022 Rive
*/
#include "pls_path.hpp"
#include "eval_cubic.hpp"
#include "rive/math/simd.hpp"
#include "rive/math/wangs_formula.hpp"
namespace rive::pls
{
PLSPath::PLSPath(FillRule fillRule, RawPath& rawPath)
{
m_rawPath.swap(rawPath);
m_rawPath.pruneEmptySegments();
}
void PLSPath::rewind()
{
assert(m_rawPathMutationLockCount == 0);
m_rawPath.rewind();
m_dirt = kAllDirt;
}
void PLSPath::moveTo(float x, float y)
{
assert(m_rawPathMutationLockCount == 0);
m_rawPath.moveTo(x, y);
m_dirt = kAllDirt;
}
void PLSPath::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 PLSPath::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 PLSPath::close()
{
assert(m_rawPathMutationLockCount == 0);
m_rawPath.close();
m_dirt = kAllDirt;
}
void PLSPath::addRenderPath(RenderPath* path, const Mat2D& matrix)
{
assert(m_rawPathMutationLockCount == 0);
PLSPath* plsPath = static_cast<PLSPath*>(path);
RawPath::Iter transformedPathIter = m_rawPath.addPath(plsPath->m_rawPath, &matrix);
if (matrix != Mat2D())
{
// Prune any segments that became empty after the transform.
m_rawPath.pruneEmptySegments(transformedPathIter);
}
m_dirt = kAllDirt;
}
const AABB& PLSPath::getBounds() const
{
if (m_dirt & kPathBoundsDirt)
{
m_bounds = m_rawPath.bounds();
m_dirt &= ~kPathBoundsDirt;
}
return m_bounds;
}
float PLSPath::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;
EvalCubic evalCubic(pts);
for (; t.x < 1; t += dt)
{
float4 p = evalCubic.at(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;
}
uint64_t PLSPath::getRawPathMutationID() const
{
static std::atomic<uint64_t> uniqueIDCounter = 0;
if (m_dirt & kRawPathMutationIDDirt)
{
m_rawPathMutationID = ++uniqueIDCounter;
m_dirt &= ~kRawPathMutationIDDirt;
}
return m_rawPathMutationID;
}
} // namespace rive::pls