| /* |
| * 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 |