| #include "rive/tess/tess_render_path.hpp" |
| #include "rive/tess/contour_stroke.hpp" |
| #include "tesselator.h" |
| |
| static const float contourThreshold = 1.0f; |
| |
| using namespace rive; |
| TessRenderPath::TessRenderPath() : m_segmentedContour(contourThreshold) {} |
| TessRenderPath::TessRenderPath(RawPath& rawPath, FillRule fillRule) : |
| m_fillRule(fillRule), m_segmentedContour(contourThreshold) |
| { |
| m_rawPath.swap(rawPath); |
| } |
| |
| TessRenderPath::~TessRenderPath() {} |
| |
| void TessRenderPath::rewind() |
| { |
| m_rawPath.rewind(); |
| m_subPaths.clear(); |
| m_isContourDirty = m_isTriangulationDirty = true; |
| m_isClosed = false; |
| } |
| |
| void TessRenderPath::fillRule(FillRule value) { m_fillRule = value; } |
| |
| void TessRenderPath::moveTo(float x, float y) { m_rawPath.moveTo(x, y); } |
| void TessRenderPath::lineTo(float x, float y) { m_rawPath.lineTo(x, y); } |
| void TessRenderPath::cubicTo(float ox, float oy, float ix, float iy, float x, float y) |
| { |
| m_rawPath.cubicTo(ox, oy, ix, iy, x, y); |
| } |
| void TessRenderPath::close() |
| { |
| m_rawPath.close(); |
| m_isClosed = true; |
| } |
| |
| void TessRenderPath::addRenderPath(RenderPath* path, const Mat2D& transform) |
| { |
| m_subPaths.emplace_back(SubPath(path, transform)); |
| } |
| |
| const SegmentedContour& TessRenderPath::segmentedContour() const { return m_segmentedContour; } |
| |
| // Helper for earcut to understand Vec2D |
| namespace mapbox |
| { |
| namespace util |
| { |
| |
| template <> struct nth<0, Vec2D> |
| { |
| inline static float get(const Vec2D& t) { return t.x; }; |
| }; |
| template <> struct nth<1, Vec2D> |
| { |
| inline static float get(const Vec2D& t) { return t.y; }; |
| }; |
| |
| } // namespace util |
| } // namespace mapbox |
| |
| const RawPath& TessRenderPath::rawPath() const { return m_rawPath; } |
| |
| void* stdAlloc(void* userData, unsigned int size) |
| { |
| int* allocated = (int*)userData; |
| TESS_NOTUSED(userData); |
| *allocated += (int)size; |
| return malloc(size); |
| } |
| |
| void stdFree(void* userData, void* ptr) |
| { |
| TESS_NOTUSED(userData); |
| free(ptr); |
| } |
| |
| bool TessRenderPath::triangulate() |
| { |
| if (!m_isTriangulationDirty) |
| { |
| return false; |
| } |
| m_isTriangulationDirty = false; |
| triangulate(this); |
| return true; |
| } |
| |
| void TessRenderPath::triangulate(TessRenderPath* containerPath) |
| { |
| AABB bounds = AABB::forExpansion(); |
| // If there's a single path, we're going to try to assume the user isn't |
| // doing any funky self overlapping winding and we'll try to triangulate it |
| // quickly as a single polygon. |
| if (m_subPaths.size() == 0) |
| { |
| if (!empty()) |
| { |
| Mat2D identity; |
| contour(identity); |
| |
| bounds = m_segmentedContour.bounds(); |
| |
| auto contour = m_segmentedContour.contourPoints(); |
| auto contours = rive::make_span(&contour, 1); |
| m_earcut(contours); |
| |
| containerPath->addTriangles(contour, m_earcut.indices); |
| } |
| } |
| else if (m_subPaths.size() == 1) |
| { |
| // We're a container but we only have 1 path, let's see if we can use |
| // our fast triangulator. |
| SubPath& subPath = m_subPaths.front(); |
| auto subRenderPath = static_cast<TessRenderPath*>(subPath.path()); |
| if (subRenderPath->isContainer()) |
| { |
| // Nope, subpath is also a container, keep going. |
| subRenderPath->triangulate(containerPath); |
| } |
| else if (!subRenderPath->empty()) |
| { |
| // Yes, it's a single path with commands, triangulate it. |
| subRenderPath->contour(subPath.transform()); |
| const SegmentedContour& segmentedContour = subRenderPath->segmentedContour(); |
| auto contour = segmentedContour.contourPoints(); |
| auto contours = rive::make_span(&contour, 1); |
| m_earcut(contours); |
| |
| containerPath->addTriangles(contour, m_earcut.indices); |
| } |
| } |
| else |
| { |
| // We're a container with multiple sub-paths. |
| TESStesselator* tess = nullptr; |
| for (SubPath& subPath : m_subPaths) |
| { |
| auto subRenderPath = static_cast<TessRenderPath*>(subPath.path()); |
| if (subRenderPath->isContainer()) |
| { |
| subRenderPath->triangulate(containerPath); |
| } |
| else if (!subRenderPath->empty()) |
| { |
| if (tess == nullptr) |
| { |
| tess = tessNewTess(nullptr); |
| } |
| subRenderPath->contour(subPath.transform()); |
| const SegmentedContour& segmentedContour = subRenderPath->segmentedContour(); |
| auto contour = segmentedContour.contourPoints(); |
| tessAddContour(tess, 2, contour.data(), sizeof(float) * 2, contour.size()); |
| bounds.expand(segmentedContour.bounds()); |
| } |
| } |
| if (tess != nullptr) |
| { |
| if (tessTesselate(tess, TESS_WINDING_POSITIVE, TESS_POLYGONS, 3, 2, 0)) |
| { |
| auto verts = tessGetVertices(tess); |
| // const int* vinds = tessGetVertexIndices(tess); |
| auto nverts = tessGetVertexCount(tess); |
| auto elems = tessGetElements(tess); |
| auto nelems = tessGetElementCount(tess); |
| |
| std::vector<uint16_t> indices; |
| for (int i = 0; i < nelems * 3; i++) |
| { |
| indices.push_back(elems[i]); |
| } |
| |
| containerPath->addTriangles( |
| Span<const rive::Vec2D>(reinterpret_cast<const Vec2D*>(verts), nverts), |
| indices); |
| } |
| tessDeleteTess(tess); |
| } |
| } |
| |
| containerPath->setTriangulatedBounds(bounds); |
| } |
| |
| void TessRenderPath::contour(const Mat2D& transform) |
| { |
| if (!m_isContourDirty && transform == m_contourTransform) |
| { |
| return; |
| } |
| |
| m_isContourDirty = false; |
| m_contourTransform = transform; |
| m_segmentedContour.contour(m_rawPath, transform); |
| } |
| |
| void TessRenderPath::extrudeStroke(ContourStroke* stroke, |
| StrokeJoin join, |
| StrokeCap cap, |
| float strokeWidth, |
| const Mat2D& transform) |
| { |
| if (isContainer()) |
| { |
| for (auto& subPath : m_subPaths) |
| { |
| static_cast<TessRenderPath*>(subPath.path()) |
| ->extrudeStroke(stroke, join, cap, strokeWidth, subPath.transform()); |
| } |
| return; |
| } |
| |
| contour(transform); |
| stroke->extrude(&m_segmentedContour, m_isClosed, join, cap, strokeWidth); |
| } |
| |
| bool TessRenderPath::empty() const { return m_rawPath.empty(); } |