| #if defined(LOW_LEVEL_RENDERING) && defined(CONTOUR_RECURSIVE) |
| |
| #include "rive/contour_render_path.hpp" |
| #include "rive/math/cubic_utilities.hpp" |
| #include <cassert> |
| |
| using namespace rive; |
| |
| // TODO when we add strokes, add ranges in the contour that need to be stroked |
| // as contiguous lines. |
| |
| // struct StrokeRange |
| // { |
| // unsigned int start; |
| // unsigned int end; |
| // }; |
| |
| class RecursiveCubicSegmenter |
| { |
| private: |
| Vec2D m_Pen, m_PenDown; |
| bool m_IsPenDown = false; |
| std::vector<Vec2D>* m_Contour; |
| // std::vector<StrokeRange> m_StrokeRanges; |
| |
| AABB m_Bounds; |
| float m_Threshold, m_ThresholdSquared; |
| |
| public: |
| RecursiveCubicSegmenter(std::vector<Vec2D>* contour, float threshold) : |
| m_Contour(contour), |
| m_Bounds(AABB::forExpansion()), |
| m_Threshold(threshold), |
| m_ThresholdSquared(threshold * threshold) |
| { |
| } |
| |
| const Vec2D& pen() { return m_Pen; } |
| bool isPenDown() { return m_IsPenDown; } |
| |
| void addVertex(const Vec2D& vertex) |
| { |
| m_Contour->emplace_back(vertex); |
| AABB::expandTo(m_Bounds, vertex); |
| } |
| |
| const AABB& bounds() const { return m_Bounds; } |
| |
| inline void penUp() |
| { |
| if (!m_IsPenDown) |
| { |
| return; |
| } |
| m_IsPenDown = false; |
| } |
| |
| inline void penDown() |
| { |
| if (m_IsPenDown) |
| { |
| return; |
| } |
| m_IsPenDown = true; |
| Vec2D::copy(m_PenDown, m_Pen); |
| addVertex(m_PenDown); |
| } |
| |
| inline void close() |
| { |
| if (!m_IsPenDown) |
| { |
| return; |
| } |
| Vec2D::copy(m_Pen, m_PenDown); |
| m_IsPenDown = false; |
| |
| // TODO: Can we optimize and not dupe this point if it's the last point |
| // already in the list? For example: a procedural triangle closes itself |
| // with a lineTo the first point. |
| addVertex(m_PenDown); |
| } |
| |
| inline void pen(const Vec2D& position) { Vec2D::copy(m_Pen, position); } |
| |
| void segmentCubic(const Vec2D& from, |
| const Vec2D& fromOut, |
| const Vec2D& toIn, |
| const Vec2D& to, |
| float t1, |
| float t2) |
| { |
| if (CubicUtilities::shouldSplitCubic( |
| from, fromOut, toIn, to, m_Threshold)) |
| { |
| float halfT = (t1 + t2) / 2.0f; |
| |
| Vec2D hull[6]; |
| CubicUtilities::computeHull(from, fromOut, toIn, to, 0.5f, hull); |
| |
| segmentCubic(from, hull[0], hull[3], hull[5], t1, halfT); |
| |
| segmentCubic(hull[5], hull[4], hull[2], to, halfT, t2); |
| } |
| else |
| { |
| if (Vec2D::distanceSquared(from, to) > m_ThresholdSquared) |
| { |
| addVertex(Vec2D(CubicUtilities::cubicAt( |
| t2, from[0], fromOut[0], toIn[0], to[0]), |
| CubicUtilities::cubicAt( |
| t2, from[1], fromOut[1], toIn[1], to[1]))); |
| } |
| } |
| } |
| }; |
| |
| void ContourRenderPath::computeContour() |
| { |
| m_IsDirty = false; |
| assert(m_ContourVertices.empty()); |
| RecursiveCubicSegmenter segmenter(&m_ContourVertices, m_ContourThreshold); |
| |
| // First four vertices are the bounds. |
| m_ContourVertices.emplace_back(Vec2D()); |
| m_ContourVertices.emplace_back(Vec2D()); |
| m_ContourVertices.emplace_back(Vec2D()); |
| m_ContourVertices.emplace_back(Vec2D()); |
| |
| for (rive::PathCommand& command : m_Commands) |
| { |
| switch (command.type()) |
| { |
| case PathCommandType::move: |
| segmenter.penUp(); |
| segmenter.pen(command.point()); |
| break; |
| case PathCommandType::line: |
| segmenter.penDown(); |
| segmenter.pen(command.point()); |
| segmenter.addVertex(command.point()); |
| break; |
| case PathCommandType::cubic: |
| segmenter.penDown(); |
| segmenter.segmentCubic(segmenter.pen(), |
| command.outPoint(), |
| command.inPoint(), |
| command.point(), |
| 0.0f, |
| 1.0f); |
| // segmenter.addVertex(command.point()); |
| segmenter.pen(command.point()); |
| break; |
| case PathCommandType::close: |
| segmenter.close(); |
| break; |
| } |
| } |
| // TODO: when we stroke we may want to differentiate whether or not the path |
| // actually closed. |
| segmenter.close(); |
| |
| // TODO: consider if there's a case with no points. |
| AABB::copy(m_ContourBounds, segmenter.bounds()); |
| Vec2D& first = m_ContourVertices[0]; |
| first[0] = m_ContourBounds.minX; |
| first[1] = m_ContourBounds.minY; |
| |
| Vec2D& second = m_ContourVertices[1]; |
| second[0] = m_ContourBounds.maxX; |
| second[1] = m_ContourBounds.minY; |
| |
| Vec2D& third = m_ContourVertices[2]; |
| third[0] = m_ContourBounds.maxX; |
| third[1] = m_ContourBounds.maxY; |
| |
| Vec2D& fourth = m_ContourVertices[3]; |
| fourth[0] = m_ContourBounds.minX; |
| fourth[1] = m_ContourBounds.maxY; |
| } |
| #endif |